3

I am working on a system that consists of different backend microservices and a few inter-linked micro-frontend apps (all of them built with ReactJS).

Below is my use case (for brevity, I'm just listing the frontend apps here and leaving out the backend services):

We have the following apps:

  1. https://id.example.com - The frontend that handles user login, profile management and presents the user with a dashboard with links to the modules he/she has access to. The dashboard has links, such as Settings, Tickets, Campaigns, Analytics, etc.
  2. We use an OAuth2 server for authentication that gives us an access_token and a refresh_token. Once these tokens are obtained, the dashboard screen is shown
  3. Now, the user clicks Tickets, which must redirect to https://tickets.example.com

When the redirect happens, I need to be able to pass the authentication information (obtained from https://id.example.com) to the Tickets app (at https://tickets.example.com), so that the tickets app can validate the token when it loads up.

I understand I can redirect the user to https://tickets.example.com?token=<auth_token>&refresh_token=<refresh_token>, but I am not sure if that is a secure way of doing it.

Tried using localStorage, but seems they are also tied up to the host and I was unable to get value from the localStorage of id.example.com from within tickets.example.com

Also, the micro-frontends are deployed separately each with its own subdomain (tickets.example.com, campaigns.example.com, etc.) and I understand that one can use frameworks such as single-spa and Module federation to load them inside a Container app. But this is not what I prefer.

In summary, I would like to know how I can pass bits of information (such as auth tokens, session information, etc.) between any two front-end apps?

Thanks, Sriram

Sriram Sridharan
  • 720
  • 18
  • 43

4 Answers4

3

I'm also new to micro frontend. This is how I solve this problem,

This will only work for parent <=> communication, not for the siblings

Let me explain: When I want to pass data from child app to parent app I dispatch action publish to the container(PUBLISH_CONTAINER) and When parent app send data I dispatch an action to subscribe to the container (SUBSCRIBE_CONTAINER) action to redux of the same app.

I have 2 apps, one is the main app(Container), the other is the micro app(MicroApp).

MicroApp App:

webpack.config.js

new ModuleFederationPlugin({
    name: 'MicroApp',
    filename: 'remoteEntry.js',
    exposes: {
    './MicroAppApp': './src/bootstrap',
    },
})

pubsub.js

export const pubsub = () => {
  let cb;
  const publishToContainer = (_cb) => (cb = _cb);
  const containerReducer = (state = 0, action) => {
    switch (action.type) {
      case 'SUBSCRIBE_CONTAINER':
        return action.payload; // Do something with the payload
      case 'PUBLISH_CONTAINER':
        cb && cb(action.payload);
        return action.payload; // This is not required
      default:
        return state;
    }
  };

  const subscribeToContainer = (store) => (state) => {
    store.dispatch({
      type: 'SUBSCRIBE_CONTAINER',
      payload: state,
    });
  };

  return {
    containerReducer,
    publishToContainer,
    subscribeToContainer,
  };
};

bootstrap.js

export * from './App';

App.js

const { containerReducer, publishToContainer, subscribeToContainer } = pubsub();
const reducers = () => {
  return combineReducers({
    containerReducer,
  });
};
const store = createStore(reducers());
export const publish = publishToContainer; // this will be used by Container app
export const subscribe = subscribeToContainer(store); // this will be used by Container app

Container App:

webpack.config.js

new ModuleFederationPlugin({
    name: 'container',
    remotes: {
        MicroApp: 'MicroApp@http://localhost:3000/remoteEntry.js',
    },
})

App.js

import { subscribe, publish } from 'MicroApp/MicroAppApp';

// Note: publish and subscribe function can be used as redux action similar like MicroApp
const microAppReducer = (state, action) => {
    switch (action.type) {
        case 'SUBSCRIBE_MICRO_APP':
            return action.payload; // Do something with the payload
        case 'PUBLISH_MICRO_APP':
            publish && publish(action.payload);
            // return action.payload; // This is not required
        default:
            return state;
    }
}

subscribe((payload) =>  store.dispatch({ type: 'SUBSCRIBE_MICRO_APP', payload })); // Micro Front end will send data here
Rahul Sharma
  • 9,534
  • 1
  • 15
  • 37
2

You can use redux for state management and share it with your other micro frontend app. You can store your auth data in the main app redux state and share it with other apps. But for that, you need to load them inside a Container app.

Redux code-splitting also helps you to share data from your other apps to the main app with splitting redux reducer and load with the main app.

Your case was different. So you can use Link component of react-router.

Example :

    <Link 
     to={{ 
      pathname: "https://tickets.example.com",
      search: "?token=<auth_token>&refresh_token=<refresh_token>",
      state: { foo: 'foo' }
     }} 
     target="_blank"
    >
     Tickets
    </Link>

The state is not working for the external links so you need to pass data in search. There is a minimum option to pass data with 2 different domains.

You can also try cross-domain-storage for allows sharing local storage across domains.

Example link : Github

Asif vora
  • 3,163
  • 3
  • 15
  • 31
1

I'm struggling to understand exactly what you want set up but here's a few things I've learned about auth in microfrontends.

In order for system A to use system B, assuming that system B requires authentication, system A would need to replicate what system B is doing. For example if system B is searching for a jwt named 'example' in localStorage, then you have to make sure that jwt is stored in localStorage with key 'example', so that when you pull in system B into system A it will automatically pick up that it's authenticated.

In your main login page you'd want to aggregate your authentication between multiple backend services because it would be a terrible user experience if you log in, then click the first navigation item and you're immediately redirected to another SSO page for a different system. The way you can achieve that is if you're able to make a password grant call to whatever OAuth server you're using. So if you're logging into system A, system A needs to do its own authentication, along with doing a password grant call for system B, then it needs to store system B's token in localStorage. Then when you pull in and use system B it will pick up that it's authenticated.

What you don't want to do is rewrite the entire authentication for every system you include, because that's a ton of work and not maintainable.

However if you do this you're entering a rabbit hole that never ends, because soon you'll want to include a system where a password grant call isn't possible, or one system is using a legacy session/cookie setup from php 3 that management said you can't change.

Along with that you might consider aggregating this auth server side and not client side, where you'd need to create an API that you can call on login in order to retrieve all the tokens for all the different systems at once. But your client will likely still need to store all the tokens in localStorage and pass it down to whatever system you've included.

The domain should have no effect on localStorage because you shouldn't be redirecting the user in the first place. The domain should stay the same, redirecting the user between systems will be a terrible user experience. Instead you want to include system B as i.e. another navigation item on system A and to have it appear as if it's the same system, no redirecting. In order to do this you'd need to pull system B's html from their server either during compile time (from their repo) or runtime (from their live server), for which you'd need CORS enabled on their served with your domain whitelisted. Then you can pull in that system completely and i.e. render system B as a new react component in your react app with its own route. You can even use that app's router as a sub router to the primary router.

Microfrontends can get really complex really quick, auth is only one of the things in this rabbit hole and no two systems work the same way so hopefully those provide you with some ideas.

0

How about setting a hidden iframe in your auth micro frontend that loads a page for your subdomain along with the token. The page that gets loaded in the iframe will have the token as query params and will set it in the localStorage.

Gan
  • 937
  • 8
  • 22