Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to configure the library with microfrontends? #32

Open
alanmatiasdev opened this issue Sep 17, 2024 · 23 comments
Open

How to configure the library with microfrontends? #32

alanmatiasdev opened this issue Sep 17, 2024 · 23 comments

Comments

@alanmatiasdev
Copy link

I have a set of micro frontends divided into a single page layout: header, content. It turns out that, in general, the platform displays the header and content at once. Since I need user information in both contexts, I include the library for both applications. This causes the library to create different states in the browser's local storage and when opening a new browser tab I get the message that no state was found in the browser. Is there any guidance for this scenario?

OIDC initialization error of type "unknown": No matching state found in storage

@garronej
Copy link
Collaborator

Hello @alanmatiasdev,

I know that we use oidc-spa at Insee in microfronted setups but I never used microfronted myself.
If you produce a simple repo with a readme that explains what you expect and what isn't working as you expect I can have a look at it.

@alanmatiasdev
Copy link
Author

alanmatiasdev commented Sep 17, 2024

I made this small project to try to exemplify the issue, but I couldn't reproduce the error that is notified, however, it reproduces the issue of multiple states in local storage (is this expected behavior?). This project simulates an application of a real case that I have. If you try to run it and encounter any difficulties, please let me know.

https://github.com/alanmatiasdev/react-mfr-oidc-example

image

I'm trying to reproduce the initial behavior: when opening a new tab, it apparently can't read the state of the local storage and returns the error above.

Update: I noticed that when opening a new tab, it searches for a state that does not exist in the local storage and this gives me the error mentioned above.

@alanmatiasdev
Copy link
Author

@garronej I managed to resolve the issue of the error mentioned above (regarding opening new tabs) by changing the userStore from sessionStorage to localStorage. I made the change because sessionStorage does not share stored data between tabs even if a new tab is from the same source. Would this be a bad practice in any way or can we implement this possibility in the library?

image

oidc-spa/src/oidc.ts

Lines 363 to 388 in a06808e

const oidcClientTsUserManager = new OidcClientTsUserManager({
"authority": issuerUri,
"client_id": clientId,
"client_secret": clientSecret,
"redirect_uri": "" /* provided when calling login */,
"response_type": "code",
"scope": Array.from(new Set(["openid", ...scopes])).join(" "),
"automaticSilentRenew": false,
"silent_redirect_uri": (() => {
let { redirectUri } = silentSso;
redirectUri = addQueryParamToUrl({
"url": redirectUri,
"name": CONFIG_HASH_RESERVED_QUERY_PARAM_NAME,
"value": configHash
}).newUrl;
redirectUri = addQueryParamToUrl({
"url": redirectUri,
"name": IS_SILENT_SSO_RESERVED_QUERY_PARAM_NAME,
"value": "true"
}).newUrl;
return redirectUri;
})()
});

However the multiple states still remain and I believe it is related to having multiple instances of the lib on the page.

@garronej
Copy link
Collaborator

First off, thank you for taking the time to investigate this so thoroughly.

Would this be a bad practice in any way, or can we implement this possibility in the library?

Unfortunately, this isn’t ideal. The whole point of opening an iframe in the background and getting the auth server to redirect to silent-sso.htm is to avoid relying on localStorage and instead use HTTP-only cookies. If you use localStorage, your app won’t pass a security audit.

Just to make sure we’re on the same page: I don’t believe there’s a known issue here, as we can restore the session using silent SSO. Can you confirm that you’re unable to reproduce any issues or suspicious behavior with the demo apps, like the TanStack Router demo app?

However, the multiple states still remain, and I believe it is related to having multiple instances of the lib on the page.

That’s concerning. You should have at most two session storage entries—something isn’t behaving as expected.

I had some trouble running your demo repo:

image

Could you update it to use my public Keycloak instance, which I use for my test project? You can check out the example here:
https://github.com/keycloakify/oidc-spa/blob/main/examples/tanstack-router-file-based/.env.local.sample

This would make it easier for me, so we don’t have to spin up a Keycloak Docker container for testing, and we can rule out any issue related to a specific Keycloak configuration.

I don’t have much experience with microfrontends, but my colleague @ddecrulle is quite knowledgeable in that area. If you can set up a straightforward reproduction path, there’s a good chance we’ll be able to figure it out.

By the way, what brought you to this library? I’m curious since it still has a relatively small user base, and I’d love to know what convinced you to give it a try.

@alanmatiasdev
Copy link
Author

Just to make sure we're on the same page: I don't believe there's a known issue here, as we can restore the session using silent SSO. Can you confirm that you’re unable to reproduce any issues or suspicious behavior with the demo apps, like the TanStack Router demo app?

I will test and return with feedback.

Could you update it to use my public Keycloak instance, which I use for my test project? You can check out the example here: https://github.com/keycloakify/oidc-spa/blob/main/examples/tanstack-router-file-based/.env.local.sample

Yes, of course. I updated the repository https://github.com/alanmatiasdev/react-mfr-oidc-example

By the way, what brought you to this library? I’m curious since it still has a relatively small user base, and I’d love to know what convinced you to give it a try.

I have been using Keycloakify for some time now, since implementing oidc-spa. I tested it in a development environment and, based on my analysis, I found the library to be more stable than the implementation we had. I chose to adopt it due to its simplicity of implementation.

@alanmatiasdev
Copy link
Author

Just to make sure we're on the same page: I don't believe there's a known issue here, as we can restore the session using silent SSO. Can you confirm that you’re unable to reproduce any issues or suspicious behavior with the demo apps, like the TanStack Router demo app?

It is working correctly. I believe this has something to do with the microfrontends configuration.

@ddecrulle
Copy link
Collaborator

Thanks for the report, I'm having the same issue as @garronej :

image

@alanmatiasdev
Copy link
Author

Strange. Did you run yarn start from the root of the project? Are all applications running correctly? Can you verify that localhost:5173/src/spa.tsx returns a vite build code?

image

@ddecrulle
Copy link
Collaborator

The issue occurred because I had something running on port 5173, and the monorepo doesn’t handle port changes well.I can reproduce now.

@garronej
Copy link
Collaborator

Can you guys give a try with 5.3.0-rc.1 ?

@alanmatiasdev
Copy link
Author

Unfortunately, this isn’t ideal. The whole point of opening an iframe in the background and getting the auth server to redirect to silent-sso.htm is to avoid relying on localStorage and instead use HTTP-only cookies. If you use localStorage, your app won’t pass a security audit.

Could we include the possibility for the user to change the storage, perhaps marking it as unsafe?

Can you guys give a try with 5.3.0-rc.1 ?

I will try and get back to you.

@garronej
Copy link
Collaborator

Could we include the possibility for the user to change the storage, perhaps marking it as unsafe?

I would prefer not to. While it's rellevent to be offered as an option for React Native or Electron desktop app, OIDC-SPA is specifically designed for web application.
In this context, persisting the session on the local sorage is fundamentally an unsafe approach and shouldn't be considered.

I will try and get back to you.

Did you had a chance to try it? There's good chances that the problem has been fixed in the latest release of oidc-spa.

@alanmatiasdev
Copy link
Author

Did you had a chance to try it? There's good chances that the problem has been fixed in the latest release of oidc-spa.

I haven't had time to check yet. I should check by the end of the week and get back to you.

@alanmatiasdev
Copy link
Author

Did you had a chance to try it? There's good chances that the problem has been fixed in the latest release of oidc-spa.

The multi-state behavior still remains in v5.4.0. I notice it occurs mainly when a new tab is opened.

image

@ddecrulle could you tell me if, in my microfrontend scenario, it would be an alternative to remove the libraries from the react application and leave it in the root (the orchestrator with the single-spa).

@ddecrulle
Copy link
Collaborator

@ddecrulle could you tell me if, in my microfrontend scenario, it would be an alternative to remove the libraries from the react application and leave it in the root (the orchestrator with the single-spa).

This approach can work but comes with significant limitations. You cannot secure anything in the remote application, and if you need tokens, it will be tricky to manage them, which will significantly impact the developer experience. Additionally, you will create implicit dependencies between the host and the remote, which could lead to maintenance challenges.

I will try to understand why there is multistate. Maybe it's related to the oidc-spa_config_hash

@ddecrulle
Copy link
Collaborator

ddecrulle commented Oct 4, 2024

The issue you're experiencing is caused by a bug in oidc-client-ts and is not related to your microfrontend setup. Specifically, when you are not logged in, each refresh of the app adds another entry to localStorage. Once you log in, this behavior stops. In your setup, this effect is being multiplied by three.

Here is a detailed explanation of the behavior:

When calling createReactOidc, an instance of UserManager is created, and the code attempts to perform a silent signin. In oidc-client-ts, this triggers this method, which calls this._signin.

This, in turn, calls the createSigninRequest method from OidcClient, which invokes SigninRequest.create. Since no id is provided, it leads to running this code: State.ts, where CryptoUtils.generateUUIDv4() is used to generate a new ID on every page refresh.

We will try to fix this.

@garronej
Copy link
Collaborator

garronej commented Oct 7, 2024

This is something that is important to us as well. I'll adress it as soon I have the time.

garronej added a commit that referenced this issue Oct 12, 2024
@garronej
Copy link
Collaborator

Hello @alanmatiasdev,

I've finaly got around fixing the problem pointed out by @ddecrulle.

Let us know if [email protected] fixes the issue.

@alanmatiasdev
Copy link
Author

Thanks for the quick response @garronej. I will test again.

@ddecrulle
Copy link
Collaborator

Hello, can you confirme that the problem is solved so I can close ?

@alanmatiasdev
Copy link
Author

The problem of multiple entries in local storage has been resolved, but this has certainly been happening frequently. When opening a new application tab, it does not recognize the user's authentication status. I have not tested it outside the context of microfrontends.

Screencast.from.27-10-2024.12.49.24.webm

@ddecrulle
Copy link
Collaborator

Great, the issue with multiple entries is fixed. I don’t understand the remaining issue.

I’ve tested it:

  • When I’m logged in, if I open a new tab, I’m still logged in (on a browser that doesn’t block third-party cookies).
  • If this issue is indeed related to third-party cookies, there’s not much we can do. The best solution would be to host the IDP on the same domain as the app.

You can find more documentation here.

To clarify:

  • This only affects Frontend 1, which can be accessed without authentication.
  • Since Frontend 2 requires authentication, it will call the IDP every time and will recognize the user.

@garronej
Copy link
Collaborator

Thanks @ddecrulle,

If you can provide a reproduction path using the creadentials of our Keycloak we can address it...

https://github.com/keycloakify/oidc-spa/blob/main/examples/tanstack-router-file-based/.env.local.sample

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants