What if you need to embed features from a Jmix, Vaadin, or Spring application into another website or web application? If your target platform isn't a portal system, the common approach is to use IFrame technology for this purpose.
However, setting up IFrames today may not be entirely straightforward.
When deploying outside your local PC, the application opened in an IFrame will likely require browser cookie support to function properly. Modern security standards dictate that cross-site cookie exchange only works when the following requirements are met:
- Both the target site and the embedded application use a trusted HTTPS setup.
- Session cookies have the Secure property enabled.
- The SameSite property is disabled for these cookies.
This means extra server configuration is required, even for testing or staging environments.
As an example, we’ll use a server with the IP address 10.5.44.78, hosting both a Docker-based Jmix application and a static website that will be served with nginx frontend server configured for HTTPS. This could be run by a virtual server or a local virtual machine running a Linux-based OS.
For production, you can purchase SSL certificates or use free options like Let’s Encrypt/ACME software. For testing purposes, we’ll set up fake domain names and map them to the server’s IP in the /etc/hosts file (located in Windows\System32\drivers\etc on a Windows PC). Add the following line to this file:
10.5.44.78 app.jmix site.jmix
After that, when you open https://app.jmix in browser
, it will send requests to IP-address we specified above.
For easier access, you can also install a public SSH key (which you may need to generate first) on the remote server using the following command:
ssh-copy-id root@10.5.44.78
The website
For this simple website, we won’t use any libraries or frameworks. Instead, we’ll write code that opens a browser-native dialog window when a link is clicked, embedding the IFrame contents.
Place the following code inside a <script>
tag in your HTML file or a separate JavaScript file:
let link = document.querySelector('a.app-link');
link.addEventListener('click', (event) => {
event.stopPropagation();
event.preventDefault();
let dialog = document.createElement('dialog');
dialog.style.width = '90%';
dialog.style.height = '80%';
dialog.insertAdjacentHTML('beforeend', `
<iframe src="https://app.jmix/pub-page" frameborder="0" height="100%" scrolling="no" width="100%" allow="cookies"></iframe>
`);
document.body.appendChild(dialog);
dialog.showModal();
});
})();
Then, add the corresponding <a>
tag in your HTML markup:
<a href=”#” class=”app-link”>Open</a>
This binds the dialog-rendering logic to a link with the class app-link, which triggers the modal when clicked.
The application
Modern browsers restrict embedding content from one host into another without proper security configurations.
The key security mechanism is the Content-Security-Policy header, specifically its frame-ancestors directive. This parameter specifies which hosts are permitted to embed your application.
In Spring Boot-based frameworks like Vaadin or Jmix, you can configure this by adding a Spring Security configuration bean:
import io.jmix.core.JmixSecurityFilterChainOrder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class IFrameSecurityConfiguration {
@Value("${application.iframeancestors}")
String iframeAncestors;
@Bean
@Order(JmixSecurityFilterChainOrder.CUSTOM)
SecurityFilterChain anonChatbotFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/pub-page").headers(headers -> {
headers.contentSecurityPolicy(csp -> csp.policyDirectives("frame-ancestors %s".formatted(iframeAncestors)));
});
return http.build();
}
}
This will give us the ability to specify allowed hosts in the application.properties file by defining the application.iframeancestors property value. If we set it as $IFRAME_ANCESTORS
, this value will be provided by a container management system like Docker Compose/Swarm or Kubernetes, or as a command line argument at application startup time. Using variables allows us to change configuration values without source code modifications or container rebuild.
As Jmix - the framework we use - is targeted for enterprise development, all screens are allowed only to be used by authenticated users that have corresponding screen IDs in their role access lists.
Since we specified the following URL address in the IFrame's src attribute: https://app.jmix/pub-page
, to have the application screen with the same URL we must create a View class annotated with both @Route("pub-page") and @AnonymousAllowed decorators. These will tell the framework to perform the required setup.
Now we are ready to build the Docker container image. To do that, run the following command in a terminal opened in the project root directory:
./gradlew -Pvaadin.productionMode=true bootBuildImage -imageName=sample-registry/app
When build is successfully finished, we can create containers running configuration in docker-compose.yml file:
services:
app-postgres:
container_name: app-postgres
image: postgres:latest
ports:
- "5432:5432"
volumes:
- postgres:/var/lib/postgresql/data
environment:
- POSTGRES_USER=app
# change this
- POSTGRES_PASSWORD=app
- POSTGRES_DB=app
app:
container_name: app
image: sample-registry/app
ports:
- "8080:8080"
environment:
- SITE_URL=https://app.jmix
- DB_HOST=app-postgres
- DB_USER=app
# change this
- DB_PASSWORD=app
- DB_PORT=5432
- DB_NAME=app
- IFRAME_ANCESTORS=site.jmix
depends_on:
- app-postgres
volumes:
postgres: {}
To make all these values applied to your Jmix or Spring application, replace hardcoded values with the variable names listed above in the application.properties file or create separate Spring profile configurations.
To run this configuration, execute the following command in your terminal console:
docker compose -f docker/docker-compose.yml up –d
You can watch startup logs with the following command:
docker logs --tail -f app
SSL-certificates
To manage self-generated SSL-certificates without hassle, we install the mkcert console tool. It has distributions for all popular platforms.
First, we must create a root certificate that will be used for SSL-certificate generation:
mkcert --install
Root certificates must be installed operating system-wide or imported in the browser to make generated SSL-certificates trusted.
To generate SSL-certificates for the application server, run the following command:
mkcert -key-file /etc/nginx/ssl/mkcert/key.pem -cert-file /etc/nginx/ssl/mkcert/cert.pem app.jmix site.jmix 10.5.44.78
We need also to install the root certificate and its key to a local PC. To do that install mkcert tool first. Then copy files from server to some local folder.
Depending on the OS vendor, cert files placement may differ. To find it run mkcert command with CAROOT argument:
mkcert --CAROOT
You may see output like below:
/root/.local/share/mkcert
A convenient way to upload or download files from the server is to use the command-line tool scp. To have it available on Windows operating systems, you may need to install Git Bash or PuTTY.
The command to run on your local PC to download files from the server may look like this:
scp root@10.5.44.78:/root/.local/share/mkcert/rootCA.pem C:\Users\user\AppData\Local\mkcert
scp root@10.5.44.78:/root/.local/share/mkcert/rootCA-key.pem C:\Users\user\AppData\Local\mkcert
The local mkcert path also depends on OS configuration. Run mkcert -CAROOT locally to determine it.
When certificate files from the server are copied, run mkcert with the install argument locally:
mkcert --install
Frontend-server
To run a frontend-server that will serve the site and proxy requests to the application, install nginx packages on the server and create the following configuration: /etc/nginx/sites-available/10.5.44.78.conf
upstream jmix-app { server 172.17.0.1:8081; }
# app.jmix http configuration
server {
listen 80;
server_name app.jmix;
location / {
proxy_pass http://jmix-app;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# app.jmix https configuration
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/nginx/ssl/mkcert/cert.pem;
ssl_certificate_key /etc/nginx/ssl/mkcert/key.pem;
server_name app.jmix;
location / {
proxy_pass http://jmix-app;
proxy_cookie_path / "/; SameSite=None; HTTPOnly; Secure";
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host:443;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto https;
}
}
# site.jmix configuration
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/nginx/ssl/mkcert/cert.pem;
ssl_certificate_key /etc/nginx/ssl/mkcert/key.pem;
root /srv/site;
server_name site.jmix;
location / {
try_files $uri $uri/ =404;
}
}
Let's explain the most important parts of this configuration.
The upstream configuration block:
upstream jmix-app { server 172.17.0.1:8080; }
defines IP address 172.17.0.1 as a source, which is used by Docker as the gateway to the external environment by default.
The following valuable line:
proxy_cookie_path / "/; SameSite=None; HTTPOnly; Secure";
performs cookie configuration magic that is required for Vaadin/Jmix IFrame applications to be opened via IFrame embeddings.
If you don't have access to frontend server configurations, try using these Spring configuration values:
server.servlet.session.cookie.same-site=none
server.servlet.session.cookie.secure=true
The parameters ssl_certificate and ssl_certificate_key in the nginx configuration allow specifying SSL-certificate and its key file locations:
ssl_certificate /etc/nginx/ssl/mkcert/cert.pem;
ssl_certificate_key /etc/nginx/ssl/mkcert/key.pem;
The root parameter specifies the website static files location:
root /srv/site;
This can be just a single index.html file with the link and script contents we created at the beginning.
The proxy set header lines:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
specify configuration for WebSocket connections to be proxied. This protocol is used by Vaadin-based technologies for performant client-server data and command exchange.
Don't forget to restart the nginx service after configuration changes for them to take effect, or even better, run the nginx -t command first to validate the configuration.
When everything is up and running, we are ready to open the https://site.jmix
URL in a browser, click the Open link, and see the IFrame with the application.