Cross-Domain sign-in Without SSO

Rahul Singh
4 min readFeb 28, 2023

Let’s first start with some theory of what we are trying to do here.

The simplest way to implement this is to go with SSO. However, when this schema is used across domains the main drawback is that the user is going to be redirected and authenticated each time he navigates to another domain due to the same-origin policy: the access token can not be shared between domains (example2.com can not access data of example1.com), so the target domain will treat the user as unauthenticated, redirecting him to the central SSO service.

To prevent the authentication service from re-requesting credentials, it is common to have a session cookie (not an access token), but there is a technique to share data across domains using browser localStorage/cookies and an iframe pointing to an intermediate domain sso.example.com.

  1. To authenticate the user in example1.com, redirect them to the authentication server in sso.example.com, issue a JWT after authenticating, and store it in the local storage of this domain.
  2. Create an iframe in sso.example.com pointing to example1.com. The sso.example.com sends the JWT token to the iframe pointing to example.com and example.com receives the token and saves it in its local storage.

There is no problem with the same-origin policy because example1.com has access to its local storage and the communication between the iframe and the parent page is allowed if the origin and target domains recognize each other.

Now that we have an idea of what is the problem and what we are trying to solve, let’s dive into the coding section of our app.

In First App, we create a reusable cached component using iframe.

import React, { memo } from 'react';
import styled from 'styled-components';
import config from '../../../config';

const CrossSigninIframe: React.FC<any> = () => {
return (
<>
<StyledIframe id="mainapp_receiver" src={`${Url to example1.com}`}>
<p>Your browser does not support iframes.</p>
</StyledIframe>
</>
);
};

const StyledIframe = styled.iframe`
display: none;
`;

export default memo(CrossSigninIframe);

So first we needed an iframe to render the Second website. Once that is done we can send or receive messages between the 2 domains. We have used React Memo here so we can get the cached version of the iframe component on re-renders, this is important as this will improve the application. we also make this iframe display CSS as none so it is not visible to the users. we only have this so we can communicate between the 2 domains.

Now when a user does the signup on the First website we get the response from the API call, the response contains the jwt token and other info required for user auth.

we call the sendMessage function with the data we want to send to the Second domain. (response from the API and extra variable for validation).

sendMessage({ isLoginData: true, data: response });
export const sendMessage = (data: any) => {
if(config.app_url){
const receiverFrame = (document?.getElementById('mainapp_receiver') as HTMLIFrameElement)
receiverFrame?.contentWindow?.postMessage(data, config.app_url);
}
};

Now we are able to send the message from the First App to Second App, now we have to receive this message on the Second website and do some checks, and validate that the one sending the message is one of the allowed domains, in our case its First website.

Second website Changes:

In the app.js file, when the component mounts we add the message event to the window so we can receive all the messages being sent to the website using postMessage

window.addEventListener('message', handleMessageFromFirstWebsite);
Funny Right? Nevermind

Now we have to make the handler function that does all the validation and does some operations when everything is valid.

const handleMessageFromFirstWebsite = (e) => {
const origin = get(e, 'origin', '');
// here we check if the domain sending the message is allowed to send the message or not. if not then we do an early exit from the function.
if (origin !== "sso.example.com") return;
const isLoginData = get(e, 'data.isLoginData', false);
const data = get(e, 'data.data', null);
// we also do check if data is valid data and if data is related to login. if so then we save the data in local storage, thus completing the auth.
if (isLoginData && data) {
saveLoginDataToStorage(data, true);
}
};

Now that data is received, validated, and saved in local storage, Now users can come to the Second domain and see that they are already logged in and don’t have to auth themself again for this domain.

Simple right? Just some small functions and you are done. But make sure you have a proper security check while transferring data from one domain to another. Make sure your second domain rejects all the messages coming from domains not related to your application.

Keep Learning and Growing.

Follow for more such posts.

--

--