Space for Fakhrul Islam
Breadcrumbs

SSO Integration in iFrame

This document explains how to implement Microsoft Single Sign-On (SSO) authentication when embedding your application inside an iFrame. Below is a diagram illustrating the authentication flow:


Note: This implementation uses Microsoft SSO for demonstration purposes only. The same authentication flow can be applied using any SSO provider of your choice, such as Google, Okta, Auth0, or your own custom SSO solution.


sso flow.png
Figure 1. Microsoft SSO Authentication Flow


The logic is structured as follows:

  1. The user lands on the page and clicks Login with Microsoft

  2. A Microsoft login popup appears via the MSAL library

  3. On successful sign-in, Microsoft returns an ID token

  4. The ID token is sent to /verify, the backend validates it and sets a secure session cookie

  5. The page calls /session to confirm authentication status

  6. If authenticated, the iFrame stream is displayed and the welcome message is shown

  7. On logout, /logout is called, the session is cleared, and the iFrame is hidden

















Video Preview













Prerequisites

Before you begin, you need a registered application in the Microsoft Entra Admin Center:

  1. Go to entra.microsoft.comMicrosoft Entra IDApp RegistrationsNew Registration

  2. Set the Redirect URI to your domain (e.g. https://yourdomain.com/)

  3. Under Authentication, enable ID tokens under Implicit grant

  4. Copy the Application (client) ID — you will need this in Step 1











Environment Setup

Before running the server, create a .env file in your project root with the following variables:

JavaScript
MS_CLIENT_ID=your-microsoft-app-client-id
JWT_SECRET=your-strong-random-secret
PORT=3000
NODE_ENV=production

Variable

Description

MS_CLIENT_ID

The Application (client) ID from your Microsoft App Registration

JWT_SECRET

A long random string used to sign the session JWT, keep this secret

PORT

The port your Express server will listen on

NODE_ENV

Set to production to enable the secure flag on cookies

Important: Never commit your .env file to version control. Add it to your .gitignore.













Follow the steps below:

Step 1. Create the Login Page

Create your main HTML page. It loads the MSAL library from CDN and references your client-side script. The CLIENT_ID from your Microsoft App Registration is injected by the server at render time so it is available to your JavaScript.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>SSO Demo</title>
    <link rel="stylesheet" href="/styles.css">
    <script src="https://alcdn.msauth.net/browser/2.37.0/js/msal-browser.min.js"></script>
    <script>
        const CLIENT_ID = "<%= clientId %>";
    </script>
    <script defer src="/script.js"></script>
</head>
<body>
<div class="container">
    <h1>SSO Demo</h1>
    <div id="userInfo">Please login</div>
    <button id="loginBtn">Login with Microsoft</button>
    <button id="logoutBtn">Logout</button>
</div>
</body>
</html>

Note: <%= clientId %> is a server-side template expression. The server replaces it with your real Microsoft Client ID before sending the page to the browser.



image-20260618-043157.png
Figure 2. Login Page










Step 2. Add the iFrame to Your Stream Page

Add an <iframe> element to the page where the stream should appear. Keep it hidden by default — show it only after /session confirms the user is authenticated.

HTML
<iframe
    id="appFrame"
    style="display:none; width:100%; height:100vh; border:none;"
    allowfullscreen>
</iframe>

Define the stream URL using the username, app name, and config name provided by Eagle 3D Streaming:


JavaScript
const IFRAME_SRC = `https://connector.eagle3dstreaming.com/v5/USERNAME/APPNAME/CONFIGNAME`;

Replace USERNAME, APPNAME, and CONFIGNAME with your values from the Eagle 3D Streaming control panel.













Step 3. Initialise MSAL in Your Script

In your script.js, set up the MSAL instance using the CLIENT_ID that was injected into the page. Place this at the very top of script.js, before any button handlers or other logic — MSAL must be initialised before it can be called.

JavaScript
const msalConfig = {
    auth: {
        clientId: CLIENT_ID,
        authority: "https://login.microsoftonline.com/common",
        redirectUri: window.location.origin + "/",
    },
};

const msalInstance = new msal.PublicClientApplication(msalConfig);

The authority is set to common so users from any Microsoft tenant or personal Microsoft account can sign in. If you want to restrict to a specific organisation, replace common with your your Microsoft Tenant ID.









Step 4. Implement /verify — Validate Token & Create Session

Where it is used: This is the first API call you make immediately after a successful Microsoft login. When the user clicks Login with Microsoft and completes the popup, MSAL returns an ID token. At this point your client code has proof that Microsoft authenticated the user, but your own backend does not know about it yet. You call /verify to hand that token to your server, which cryptographically validates it and creates a trusted server-side session. Without this step, anyone could pass a fake token and gain access, the server-side validation is what makes the authentication secure.

Once /verify succeeds, the browser automatically holds a session cookie. All subsequent API calls (/session, /logout) use this cookie to identify the user, you do not need to pass the token again.



POST /verify — Server Implementation

JavaScript
// Verify Microsoft ID token and create session cookie
app.post("/verify", async (req, res) => {
    const { token } = req.body;
    if (!token) return res.status(400).json({ error: "Missing token" });
    try {
        // Decode token header and payload
        const decoded = jwt.decode(token, { complete: true });
        const tenantId = decoded.payload.tid; // or your tenant ID
        const discoveryUrl = `https://login.microsoftonline.com/${tenantId}/v2.0/.well-known/openid-configuration`;
// 1️⃣ Fetch OpenID config
        const { data: openidConfig } = await axios.get(discoveryUrl);
// 2️⃣ Get JWKs URI
        const jwksUri = openidConfig.jwks_uri;
// 3️⃣ Fetch keys
        const { data: jwks } = await axios.get(jwksUri);
// 4️⃣ Find key
        const key = jwks.keys.find(k => k.kid === decoded.header.kid);
        if (!key) return res.status(401).json({ error: "Key not found" });
// 5️⃣ Convert JWK to PEM and verify
        const pubKey = jwkToPem(key);
        const verified = jwt.verify(token, pubKey, { algorithms: ["RS256"], clockTolerance: 5 });
        // Verify audience
        if (verified.aud !== process.env.MS_CLIENT_ID)
            return res.status(401).json({ error: "Invalid audience" });
        // Create app session cookie
        const appSession = jwt.sign(
            { email: verified.preferred_username, name: verified.name },
            process.env.JWT_SECRET,
            { expiresIn: "1h" }
        );
        res.cookie("app_session", appSession, {
            httpOnly: true,
            secure: process.env.NODE_ENV === "production",
            sameSite: "lax",
        });
        res.json({ message: "Token verified", user: verified });
    } catch (err) {
        console.error(err);
        res.status(401).json({ error: "Token verification failed" });
    }
});



POST /verify — Client Call

JavaScript
await fetch("/verify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token: idToken }),
});



Field

Type

Description

token

string

The ID token returned by MSAL after a successful login popup



POST /verify — Response (success)

{ "message": "Token verified" }



POST /verify — Response (failure)

Status

Error

400

{ "error": "Missing token" }

401

{ "error": "Key not found" }

401

{ "error": "Invalid audience" }

401

{ "error": "Token verification failed" }











Step 5. Implement /session — Check Auth Status

Where it is used: Call /session in two situations: on every page load to restore the UI state for a returning user, and immediately after a successful /verify call to confirm the session was created and render the welcome message. The endpoint reads the session cookie set by /verify and returns the user's name and email if the session is still valid. Your UI logic should use data.loggedIn to decide whether to show the iFrame stream or the login button.


GET /session — Server Implementation

JavaScript
app.get("/session", (req, res) => {
    const { app_session } = req.cookies;
    if (!app_session) return res.json({ loggedIn: false });
    try {
        const user = jwt.verify(app_session, process.env.JWT_SECRET);
        return res.json({ loggedIn: true, user });
    } catch (err) {
        return res.json({ loggedIn: false });
    }
});



GET /session — Client Call

JavaScript
const res = await fetch("/session");
const data = await res.json();


GET /session — Response (authenticated)

{
  "loggedIn": true,
  "user": {
    "name": "Jane Doe",
    "email": "jane@example.com"
  }
}




GET /session — Response (not authenticated)

{ "loggedIn": false }




Use data.loggedIn to control whether the iFrame stream is shown. If true, set the iFrame src to your stream URL and make it visible. If false, clear the src and hide it.

Note: The session expires after 1 hour. After expiry, /session returns { "loggedIn": false } and the user will need to log in again.













Step 6. Implement /logout — Clear Session

Where it is used: Call /logout when the user clicks the Logout button. This endpoint clears the session cookie on the server so the session can no longer be used. After calling it, reset your UI to the logged-out state — hide the iFrame, clear its src, and show the login button again. The pixel stream will stop loading as soon as the src is cleared.



POST /logout — Server Implementation

JavaScript
// Logout endpoint
app.post("/logout", (req, res) => {
    res.clearCookie("app_session", {
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        sameSite: "lax",
    });
    res.json({ message: "Logged out" });
});



POST /logout — Client Call

await fetch("/logout", { method: "POST" });




POST /logout — Response

{ "message": "Logged out" }


After this call, a subsequent /session request will return { "loggedIn": false }











API Endpoints Summary

Endpoint

Method

Purpose

/verify

POST

Validates the Microsoft ID token and creates a session cookie

/session

GET

Returns current login status and user info

/logout

POST

Clears the session cookie and ends the session












Troubleshooting

Issue

Cause

Fix

Login popup is blocked by the browser

loginPopup() was called outside of a direct user action

Always trigger loginPopup() inside a button click handler; never call it from window.onload or any automatic execution

/verify returns 401 — Token verification failed

The CLIENT_ID on the server does not match the one used in MSAL

Ensure the Microsoft App Registration Client ID is correctly set in both server environment and the CLIENT_ID used in the frontend

/verify returns 401 — Key not found

Token was signed with a key not present in Microsoft JWK keyset

Token may be malformed or from the wrong tenant; log out and sign in again

Session is lost after page refresh

Secure cookie flag requires HTTPS, but server is running on HTTP

Use HTTPS in production environment

iFrame stream restarts every time page is focused

iframe.src is being reassigned on every checkSession() call

Prevent reassigning: if (iframe.getAttribute("src") !== IFRAME_SRC) before setting it

Logout button visible before login

Button is not hidden by default in HTML

Add style="display:none;" to logoutBtn; note checkSession() is async and runs after initial render

MSAL authority mismatch error

App registration is single-tenant but common authority is used

Change authority to https://login.microsoftonline.com/YOUR_TENANT_ID









 






Need help?

If you need any assistance, feel free to reach out through any of the following channels:

🛠️ Support Portal: Contact Our Support Team

💬 Discord Community (Faster Support): Join Our Discord Community

📧 Email Support: support@eagle3dstreaming.com

 

🆓 Get Started for free

 

Follow us on:

Facebook | GitHub | LinkedIn | YouTube