keycloakify / oidc-spa

Openid connect client for Single Page Applications
https://www.oidc-spa.dev
MIT License
48 stars 6 forks source link

Third-party cookie will be blocked #18

Closed itsam closed 6 months ago

itsam commented 6 months ago

Hello, thank you for your effort. Probably you are aware of this.

I am running the example with Tanstack Router. Works perfectly, but in the Chrome console I am getting the warning "Third-party cookie will be blocked". The same goes with keycloak.js as well.

Any ideas how to overcome this issue?

Thank you in advance

garronej commented 6 months ago

Yes sorry,
I'm well aware of this but since I haven't released this library officially yet I though no one would notice until I address this but you did.

For the rest of my answer I'll assume you're a Keycloak user since you probably found this library via the Keycloakify starter repo.

In the examples we have the warning because the Keycloak server I'm currently using isn't under the same root domain than the demo app. I will migrate it tomorrow but the key takeway here is that, if you want to have silent sign in to work properly after 3rd party cookies phased out, you will have to have your Keycloak server and your app sharing the same root domain.

For example:

Is ok. But:

Isn't okay.

As you can notice we don't have the warning in the Keycloakify starter deployment since:

If you can't have your keycloak server and your app sharing the same root domain, and only in that case, silent signin won't work properly after Chrome phase out third party cookies (they are already disabled by default on Safari by the way).
This mean that when a user return to your app he will have to click "login" again, he won't need to re-enter he's credential though, but the page will be fully refreshed which isn't ideal.
There is no way around but this isn't the end of the world. I will provide an option to disable silent signin for thoses in this perticular situation so no warning is displayed.

See keycloak resources about this:

I hope it's clear,

itsam commented 6 months ago

I really appreciate your prompt reply. You response is crystal clear. Thank you.

In my case it's doable to have both client and keycloak under same domain so no problem.

The library although still not officially published is very promising. I am going to use it to a pre-production by this Friday :)

One minor notice (maybe I should have opened a new issue..anyway): Is there a reason why you are providing decoded the idToken and not the accessToken instead?

The reason I ask is because I want to extract the roles of the user and now I have to jwt_decode accessToken separately in order to have this info (similar to hasRealmRole or hasResourceRole of keycloak.js) Best.

garronej commented 6 months ago

Thank you for the kind words, they are really appreciated!

One minor notice (maybe I should have opened a new issue..anyway): Is there a reason why you are providing decoded the idToken and not the accessToken instead?

Well, the access token is usually a JWT but is not guarenteed to be one and is not supposed to be decoded by the client application. It's something that you use to autenticate to an API, you use it as a barrer token.

The ID token on the other end is always a JWT and it's purpose is to convey info about the user, it can be decoded client side.

I think the information you are looking for are in the ID token, they are just tripped out by zod in the examples.

image

src/oidc.ts

 import { createReactOidc } from "oidc-spa/react";
 import { z } from "zod";

 export const {
     OidcProvider,
     useOidc,
     prOidc
 } = createReactOidc({
     clientId: import.meta.env.VITE_OIDC_CLIENT_ID,
     issuerUri: import.meta.env.VITE_OIDC_ISSUER,
     publicUrl: import.meta.env.BASE_URL,
     decodedIdTokenSchema: z.object({
         sub: z.string(),
         preferred_username: z.string(),
+        realm_access: z.object({
+            roles: z.array(z.string())
+        })
     })
 });

I should document this better, the fact that zod stripes out everything else can be missleading.

itsam commented 6 months ago

Hi, Documentation is clear about z on optional decodedIdTokenSchema, so no worries on this :) The issue is (at least in my case using latest keycloak) the following

    const { oidcTokens } = useOidc({ assertUserLoggedIn: true });
    console.log(oidcTokens)

gives:

accessToken: "xxxxx",
idToken: "yyyyyy",
decodedIdToken: {...}
...

decodedIdToken is actually the JWT decoded idToken and my suggestion is to JWT decode accessToken instead which has more rich data inside comparing to idToken. Then of course you can apply the optional decodedTokenSchema

For example realm_access, roles, etc are included only in accessToken and NOT in idToken which mostly contains info about user real names, etc

Is your screenshot depicts the decodedIdToken?

Hope I make myself clear. Thank you in advance

garronej commented 6 months ago

Is your screenshot depicts the decodedIdToken?

Yes it is, you see, that's the thing. Zod strips away all the field not explicitely validated in the schema. This might leave you under the impression that there's fewer thing in the id token that there actually is.

Try this:

    decodedIdTokenSchema: {
        parse: (decodedIdToken) => {

            const zodOutput=  z.object({
                sub: z.string(),
                preferred_username: z.string()
            }).parse(decodedIdToken);

            console.log({ decodedIdToken, zodOutput });

            return zodOutput;

        }
    }

And see what's actually in the decoded id token compared to the zod shema parse output.

I'm fully willing to provide a parsed access token if it turns out there's actually more information in it but to my understanding there's not.
Or if there is, it's an explicit policy on the Keycloak server. In principle the Acess token is soposed to be opack for the client.

itsam commented 6 months ago

OK, your answer led me to solution. You are right that accessToken should be left untouched. I search a little bit and indeed is was a matter of keycloak settings. Thanks a lot for you help. The longer with hands-on oidc-spa, the more I appreciate it :)

For anyone looking how to setup keycloak to include roles in idToken, take a look at the last comment in https://keycloak.discourse.group/t/how-to-get-user-roles-in-access-token/3289/5

garronej commented 6 months ago

Let's go!
Thanks for the feedback and the linked resource!

I'm very happy you're enjoying your experience with oidc-spa! <3

Just to let you know, what I'm currently working on:

itsam commented 6 months ago

Let's go! Thanks for the feedback and the linked resource!

I'm very happy you're enjoying your experience with oidc-spa! <3

Just to let you know, what I'm currently working on:

  • Providing a way to auto logout the user if he's unactive and optionally displaying a countdown "are you still there? You will be automatically logged out in 5...4...3..."
  • Providing a mock of the API so that the app can be used in test environements like Storybook.

wow!!! auto logout with a counter will be an amazing feature :) Can't wait :) At first chance I will share my use case using oidc-spa. I can provide you next week an example with tanstack router latest version with file based routers (as suggested) and vite (current example uses some deprecated functions).

Lets GO 👍

garronej commented 6 months ago

Hey @itsam,

oidc-spa now support auto logout and provide an easy way to display a countdown timer!

https://docs.oidc-spa.dev/documentation/auto-logout

itsam commented 6 months ago

@garronej just tried it and it works perfectly :) Thanks a lot for your effort. Amazingly simplistic library that does what it says 🥇 In combination with Tanstack Router is a very powerful tool and I have used a lot of different options before. Never go back

garronej commented 6 months ago

Thank you very much @itsam, it really made my day!
As a matter of fact, if you're okay with it I'll use your message as promotion material.

PS: There was a bug in the last release that I just fixed, an infinite look when calling oidc.renewToken() (It's not something that should affect you since renewToken() don't need to be manually called but it's best to update anyway).

itsam commented 6 months ago

@garronej you are free to use the quote :) I will do the update as well. Thanks