appsup-dart/openid_client

How to avoid redirect to Keycloak on page reload?

adrian-moisa opened this issue · 8 comments

I've managed to implement the OIDC authorisation PKCE flow on Flutter web using the following tutorial: https://medium.com/@rangika123.kanchana/keycloak-integration-for-flutter-web-using-openid-client-with-authorization-code-flow-489afeac6e9f . Overall everything seems to be working fine. Silent refresh of the token during the same session works just fine. Now I'm struggling with the following problem:

  • Whenever I reload the app it will always redirect to keycloak and then it instantly redirects back to app. The instant redirect to web app happens because the user was already authenticated in keycloak, therefore the login form is not displayed.
  • Most web apps that I remember using don't redirect to an auth screen/url on page reload. It's all seamless/silent. Is it possible to do the same with openid_client?
  • I looked on the web for various answers and I understand that in general it's a good practice to avoid caching refresh tokens in session storage / local storage or cookies to avoid the possibiltiy of XSS attacks.
  • Reading this article I learned that there's a technique available to deal with this problem. It's called refresh token rotation. In a nutshell, each server request ends up generating a new refresh token that can be used only once. According to this article such a token is safer to store in local storage (despite not being a secure storage place). Please contradict me if this information is wrong. https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
  • https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation - More about the refresh token rotation technique.
  • From other searches I understand that one can init the openid_client with a refresh token and have it ready to make new requests to the resource server (authorised REST api). In theory (if I understand right) a one time use refresh token can be stored in local storage, the app reloads, checks local storage, finds the token, initialises the client and thus it can skip the mandatory redirect to keycloak.
  • As far as I understand, keycloak has a setting where one can enable token rotation by enabling "Revoke Refresh Token". Reading more I understand that this setting introduces new issues when multiple tabs are open on the same app. One tab consumes the latest token and then the other tabs enter in an invalid state that requires a new redirect. Again, this is behaviour that I don't see in most apps such as jira, fb, or whatever.
    keycloak/keycloak#10937
    keycloak/keycloak#14122
    authts/oidc-client-ts#967
  • Additional subpoint, for native I've used the native Authenticator since it supports PKCE. I see there the same behaviour, except everything happens much faster since the emulator does not need to download the app. I guess here we don't care that much about the relaod behavior since users don't really do reloads. Maybe opening and starting the app from scratch has the same visual behavior. Then again, I guess I have to use some secure storage solution for the same goal of not getting redirected to keycloak login.
  • Also extra question. Why is the current web Authenticator supporting the implicit flow instead of authorisation + PKCE? I understand the implicit flow is outdated/insecure and it will be deprecated in OAuth2.1.

So all in all, my question is, can I avoid the mandatory keycloak redirect safely? Or is storing refresh tokens in local storage (even with token rotation) a bad idea and should be avoided. If so is there any way at all to avoid the redirect? Having the app loaded and intialised twice at each page refresh is rather annoying. Especially during the frequent hot reloads in development. Any advice would be much appreciated. A code sample even more. Cheers!

First of all, I'm no specialist in keycloak, nor in security. This package only implements openid flows, the storage of credentials is not within the scope of this package.

Although, it is sensitive for xss attacks, I believe that many apps do store the refresh token in local storage. I think, you could also use http-only cookies, which are not readable by scripts and therefore not sensitive for xss attacks.

You could also use a technique called silent sign-in. This would imply doing the redirect in an invisible iframe. The page that loads after successful sign-in should then be able to communicate with the parent page and send the credentials.

I'll have to research more the silent sign in how it is done. It seems that this is the way forward. Also http-only cookies could help on this path. Thank you for the indications. If you don't mind I'll keep it open in case there are other users that can provide some more indications.

I've managed to implement the OIDC authorisation PKCE flow on Flutter web using the following tutorial: https://medium.com/@rangika123.kanchana/keycloak-integration-for-flutter-web-using-openid-client-with-authorization-code-flow-489afeac6e9f . Overall everything seems to be working fine. Silent refresh of the token during the same session works just fine.

Hi Adrian, I'm trying to implement the same thing. Do you still have a code example around?

tbh I wouldn't share the sample. It's a complete mess. I mean it works but it overrides the web implementation of openid_client because they use implicit as far as I can understand their code. Implicit is discouraged. I wanted PKCE on web as well. They do it only on mobile. Also the current setup ends up doing 2 loads of flutter bundle. Which is a massive slow down. Once to load the lib, then redirect then to start the app logged in. I want to implement an inline mask login to get rid of this double login. Plus, right now the web version does not have token refresh. One more problem. Overall I see that I have to do so much patching to this lib that I'm already thinking that maybe I will go with some vanilla oauth lib and just follow the general recipe to login. By now I have a slightly better understanding of the entire ouath thing and I can venture into doing something cleaner with less clutter.

tbh I wouldn't share the sample. It's a complete mess. I mean it works but it overrides the web implementation of openid_client because they use implicit as far as I can understand their code. Implicit is discouraged. I wanted PKCE on web as well. They do it only on mobile. Also the current setup ends up doing 2 loads of flutter bundle. Which is a massive slow down. Once to load the lib, then redirect then to start the app logged in. I want to implement an inline mask login to get rid of this double login. Plus, right now the web version does not have token refresh. One more problem. Overall I see that I have to do so much patching to this lib that I'm already thinking that maybe I will go with some vanilla oauth lib and just follow the general recipe to login. By now I have a slightly better understanding of the entire ouath thing and I can venture into doing something cleaner with less clutter.

Hi Adrian, thanks for your reply.
I did not know that the web version of the library used Implicit Flow. So I am passing the “Code Verifier” and “Code Challenge” but it is not actually used?
In addition, I also have the problem with double login.
I have to double click the “login” button that calls the authenticateWeb() function to log in.
I need to find a solution.

@alex27riva have you progressed on this issue? I'm currently researching flutter_web_auth_2 but I expect the double loading problem there as well. At the moment I'm thinking to write some logic in index.html to check if there is anything stored in local storage related to access tokens. If not straight away redirect to IAM login. Another thing that I am looking for is silent refresh of the token. Since I did my own web Authenticator implementation that uses authorisation flow it does not have the token refresh technique. I might as well implement a lib that has them both.

Another solution I was thinking of was to write an embedded login. But the more I read on the web the more I understand that embedded login can't be easily implemented without some major security problems. Apparently the redirect is the lasy way to ensure average Joe devs like us can't duck it up really bad. I've seen that Okta provides an embedded client lib for js SPAs. So I get proof this is possible for people who know how to do it.

@alex27riva have you progressed on this issue? I'm currently researching flutter_web_auth_2 but I expect the double loading problem there as well. At the moment I'm thinking to write some logic in index.html to check if there is anything stored in local storage related to access tokens. If not straight away redirect to IAM login. Another thing that I am looking for is silent refresh of the token. Since I did my own web Authenticator implementation that uses authorisation flow it does not have the token refresh technique. I might as well implement a lib that has them both.

Another solution I was thinking of was to write an embedded login. But the more I read on the web the more I understand that embedded login can't be easily implemented without some major security problems. Apparently the redirect is the lasy way to ensure average Joe devs like us can't duck it up really bad. I've seen that Okta provides an embedded client lib for js SPAs. So I get proof this is possible for people who know how to do it.

Hi Adrian, I ditched openid_client library and I migrated to oidc package.
Now the login and logout works perfectly and I completed my Oauth2 project.

Hi @alex27riva. Good to hear you making progress. I also moved away from openid_client. I use flutter_web_auth_2. Same experience, now I get a far more cleaner implementation. To be fair to openid_client also my understanding of oauth2 has improved and I was able to code cleaner. However, there are some notable improvements with the new lib. It has support for custom tabs and most importantly, it communicates back from the login mask via activity and intent messages. No longer using session storage to pass the access token. Safe travels ahead!