-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Implement refresh token rotated feature for public clients gh-297 #335
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR looks like resolving the issue. Let us wait for feedback from other members.
Thanks for the PR @boggard. OAuth 2.0 for Browser-Based Apps documents the best practice for refresh tokens, however, it also states:
We're going to apply gh-296 for the I'll keep this PR open for now and we'll revisit this later and possibly merge this feature or provide a customization hook that would allow applications to provide this feature. |
@boggard I'm going to close this PR as the current implementation of OAuth2AuthorizationCodeAuthenticationProvider does not support issuing refresh tokens to public clients. We have limited support for Public Clients at the moment but we do plan on building further support later on at some point. Please keep track of gh-297 as when we start that work, then would be a good time to resubmit this PR. Thanks. |
In my opinion the leakage of refresh_token by public clients is as dangenerous as the leakage of session ids. Brutely banning the issuing of refresh tokens to public clients really limits the use cases of this project to public clients, which is really annoying at this stage. |
A better solution would be to make it configurable, before you can fully implement gh-297. |
Can you please provide reason(s) why this limits use cases for public clients? Why is a refresh_token needed? Why can't the public client obtain a new access token after the existing one is expired? |
@jgrandja as someone who's been following this issue, I hope you don't mind the interjection. Correct me if I'm wrong but obtaining a new access token would require the end-user to re-enter their credentials, which for mobile apps is less convenient than using a refresh token. We could perhaps use longer-lived access tokens, but (as I understand it) those are more difficult to revoke than refresh tokens if needed. |
Thanks for reply, and my thoughs are already expressed by chrisrhut. |
Thanks for the feedback @chrisrhut @zhenchuan9r. After we release |
Following, I'm also working with public clients and find a long lived access token not ideal in this case. |
@chrisrhut @zhenchuan9r @nickmelis Just putting this out there in case you are not aware of the Backend-for-Frontend (BFF) design pattern? You might want to look into this as this is the recommended approach for securing access tokens (and refresh tokens) using a SPA. See this comment for additional context. As I'm sure you are aware, storing (or passing through) access tokens or refresh tokens in the browser via a SPA is not secure and discouraged. |
IMHO even though the BFF pattern is great and can work in some cases, it's too intrusive and inefficient for others: when you have multiple clients, you use a CDN for serving the SPA, etc. PKCE was meant to be used by public clients that can't store secrets but I think that being able to get a refresh token when using this flow is highly needed. If there isn't a way to refresh the token, we basically have two alternatives:
I guess that most developers will end up setting really long I think that allowing refresh tokens shouldn't be that bad if we follow good practices: they must be retrieved using a secure communication (https and PKCE take care of that), they shouldn't be stored in local or session storage (use memory instead), and refresh tokens should be rotated in order to mitigate the consequences of a leak. |
Thanks for the feedback @aberasarte. We'll be looking into this shortly. |
@boggard @zhenchuan9r @chrisrhut @nickmelis @aberasarte I reviewed the relevant specifications in detail and provided a high-level summary and final recommendation for best practices when developing browser-based apps. Please see gh-297. |
@jgrandja I appreciate the careful consideration you and your team have given this, but I'm still a little bit at a loss. Your comment mentions Token Mediating and Session Information Backend For Frontend - but that document says (emphasis mine):
My hope had been to use spring-authorization-server to do exactly that - establish the secure session between a mobile client and the server - using OAuth2 (JWT access and refresh tokens). It sounds like the only alternative is to use an alternate mechanism (a la "plain" spring-security backend OIDC login and session tokens) to do so, but as zhenchuan9r mentioned in the comments above, I'm not seeing the risk distinction between a long-lived opaque session token (which can be leaked) and a long-lived refresh token; in fact as they mentioned the former is somewhat more risky as there's no standard for token revocation. It seems (and forgive me if I'm misinterpreting) you're suggesting that generally speaking, mobile apps just shouldn't provide long-lived sessions - or if they do, you want to make sure that it's not spring-authorization-server that's allowing it? |
@chrisrhut Regarding your comment:
Authentication is NOT a concern of an OAuth 2.0 Provider, it's only concern is Authorization. This is the basis of OAuth 2.0, where Authorization Servers mint tokens and Resource Servers authorize tokens. Although, OpenID Connect 1.0 was introduced to address Authentication, you still need to leverage an Authentication sub-system integration with the OAuth 2.0 Provider, e.g. LDAP, SAML, Federated OIDC (social login), custom form login, etc. As far as an "authenticated session" goes, this is the responsibility of the Authentication sub-system that is integrated with the OAuth 2.0 Provider (Spring Authorization Server). As I'm sure you are aware, a client cannot obtain an access token using the
The backend would need to be configured with some form of authentication mechanism to implement this pattern. It would store the sensitive data (access tokens, refresh tokens, etc.) and could only be fetched by an authenticated secure session between the frontend and backend. Spring Security provides many different integration options for various authentication mechanisms, so I would recommend looking there. However, I still favour the BFF pattern because tokens NEVER pass through the browser agent, which is what I would ensure when building an SPA. The bottom line is that the browser agent is NOT a secure environment, so I personally would not allow tokens to pass through or even worse store them in storage/memory. As far as session-related attacks, e.g. session fixation, Spring Security provides out-of-the-box protection against well-known vulnerabilities, see Session Management. Finally, our ultimate goal is to build the most secure OAuth 2.0 / OIDC framework for the Java platform and limit the potential attack surface. Therefore, we need to ensure we don't introduce features that could potentially introduce a vulnerability down the road. Allowing Public Clients to obtain refresh tokens opens things up so this is not something we want to introduce. |
@jgrandja I think your final paragraph drives it home and that's definitely an understandable stance! It sounds like there might be a slight mismatch with our use case, but I wanted to thank you for taking the time to respond with such consideration. 🙏 I didn't intend to derail this thread/issue and make it my personal technical support line, but hopefully all of this is helpful added context for anyone who follows too. |
Does that mean the option of issuing access and refresh tokens to public clients will never find its way into spring-authorization-server? Not even as a disabled-by-default config option? |
Not at all. I'm glad you understand our stance on this and that is all I wanted to communicate to you and the community. We are doing our best to make Spring Authorization Server accessible to ALL users including Javascript developers. But at the same time, we need to keep the balance between accessibility and security and therefore need to ensure we always keep to a very tight security posture. FYI, we're doing a webinar on Spring Authorization Server and best practices developing a SPA on Mar 10. You might be interested in checking it out. You can register here. |
A public client is able to obtain an access token using As far as |
Hi @jgrandja First, thanks for taking the time to answer all these concerns, they are really insightful especially in understanding why having a refresh token for public client is a bad idea. Although, I think it would be bit forceful to make users stop using the 'Remember Me' functionality on front end clients. My opinion on the BFF pattern is that its not meant to act as a security layer, rather a consolidation layer for microservices. It does not solve the issue of the 'Remember Me' functionality in terms of security, and its not feasible option for smaller projects. This may affect or force dev's to use confidential client with authorization code flow, storing the private key on client just so that they do not break existing functionality. This would seem to be a bigger security risk than having refresh token being leaked in my opinion. I agree to what @nickmelis mentioned at least having refresh-token disabled as a default configuration, or forcing the dev to implement DPoP for refresh token, would seem to be a better approach. |
Hi @jgrandja, As others already said, thanks for your thorough explanation about the That being said, I really appreciate the work you are doing in this project. It's a great alternative for those who want a lightweight Authorization Server in the Java/Spring ecosystem. Keep up the good work 👌 ! |
Just to add some more context to @aberasarte's comment, we recently moved from Auth0 to our own auth server, and Auth0 too supports sending refresh tokens together with access token with the PKCE flow (it's disabled by default though). |
I'm not seeing the connection between "Remember Me" functionality and Refresh Token? Are you using a Refresh Token for the Remember Me token? Can you provide more insight on your usage as your comments are not that clear to me. |
@aberasarte @nickmelis Can you provide me links to the documentation for Refresh Token public client support for any/all of the providers you listed -> Google, Microsoft, Hydra, Okta, Auth0. I'd like to dig into it a bit more and see how they have implemented it. The one major question I have is how do those providers authenticate the public client when it performs the |
Sure @jgrandja , In the legacy OAuth server, front end clients would implement the remember me functionality by using refresh tokens to get new access tokens without the need for the user to go through the authorization flow every time. This article explains how it was done: https://www.baeldung.com/spring-security-oauth2-remember-me Also we have built apps with google sign-in using this guide -> https://developers.google.com/identity/protocols/oauth2/native-app The refresh token will be returned after authorization. |
@jgrandja I haven't used Auth0 in production yet, but we're planning to migrate to it eventually so I have looked at its documentation on this subject. I believe the relevant pages are https://auth0.com/docs/secure/tokens/refresh-tokens and https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation. There is also this blog post introducing the topic in 2020 - https://auth0.com/blog/securing-single-page-applications-with-refresh-token-rotation/. The Auth0 SPA SDK has docs on how to use rotating refresh tokens: https://auth0.com/docs/libraries/auth0-single-page-app-sdk#use-rotating-refresh-tokens |
@jgrandja probably redundant but we followed this document: https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-proof-key-for-code-exchange-pkce |
Sure!
|
@rayman245 The article you referenced OAuth2 Remember Me with Refresh Token is quite an unusual pattern. To the point, a refresh token should NOT be used as a user authentication token. Remember Me functionality typically stores user authentication context information via a cookie. Because this cookie contains user authentication context, a pre-authentication scenario is possible. On the other hand, a refresh token is an authorization grant NOT an authentication token containing user context information. It's meant to be used by the client to exchange an expired access token. Furthermore, the article states the following:
This article does not apply to the flow being discussed in this ticket. The flow in the article is
This article describes Native (Mobile and Desktop) apps NOT JavaScript (SPA) apps. The section Step 5: Exchange authorization code for refresh and access token demonstrates how to refresh an expired access token using a Confidential Client ( The article OAuth 2.0 for Client-side Web Applications is meant for SPA's, however it references usage of the |
I mentioned that the refresh token is used to retrieved a new authentication token, just without the need for users to go through authentication flow each time.
Also according to OAuth mobile apps are considered public clients please refer to -> https://oauth.net/2/client-types/ |
@aberasarte The Google reference you provided:
... uses a Confidential Client NOT a Public Client when authenticating on the I provided more details in this comment:
|
As of RFC8252 (OAuth 2.0 for Native Apps) it recommends to use authorization grand with refresh tokens. But it also states that native apps should still be classified as public (section 8.4). My understanding is that public native clients should be able to obtain a refresh token even if they are considered public (and do not have a client secret). |
Can you please provide the exact reference in the relevant spec that states this? Even though native clients are considered public, how do they authenticate during the If a public client receives a refresh token but is not able to authenticate itself on the token endpoint, then the refresh token is useless, since the exchange will simply fail. NOTE: If a native client is assigned credentials then the exchange will succeed, however, the native client in this particular flow is considered a confidential client since it uses its credentials to authenticate on the token endpoint. |
RFC6749 - Section 9 (Native Applications)
RFC6749 - Section 10.1 (Client Authentication)
If I understand this these two parts correctly then native apps should not be treated as confidential clients and should not have a client secret issued to them. Furthermore, as stated in the next paragraph. Clients without a secret should consider extra measures to limit exposure of for example refresh tokens. This is where the extra security considerations of RFC8252 OAuth 2.0 for Native Apps - Section 8 should be used.
As stated in the shown paragraphs below, the token endpoint does NOT REQUIRE a client secret if the client is not confidential. RFC6749 - Section 4.1.3 (Authorization Code Grant - Access Token Request)
This is also true when using a refresh token: RFC6749 - Section 6 (Refreshing an Access Token) RFC6749 - Section 6 (Refreshing an Access Token
|
@jrmcdonald @aberasarte @nickmelis Auth0 does in fact support refresh tokens for public clients. Key points in the Refresh Tokens docs:
Key points in the Get Refresh Tokens docs:
Key points in the Refresh Token Rotation docs:
Based on my analysis, Auth0 supports refresh tokens for public clients based on the following conditions:
The KEY POINT that I want to relay here is that even if all the above conditions are met, an exposure is still possible. Given the following scenario where a legitimate public client (SPA) receives an access token and refresh token via an OIDC auth code flow and subsequently a malicious client obtains the refresh token by leakage via browser, and the malicious client uses the refresh token before the legitimate client does than the exposure has happened. Assuming access tokens expire every 1 hour, the exposure can last up to one hour. So although Refresh Token rotation helps reduce the threat, it DOES NOT completely eliminate it in this specific scenario. The other implementation behaviour that does not sit well with me is that a Public Client can perform the The bottom line is that Refresh Token rotation helps REDUCE the threat of refresh token leakage it DOES NOT ELIMINATE the threat completely. Therefore, enabling the above capabilities effectively "opens up" the potential attack surface, which obviously reduces the overall security posture. We really are trying to figure out a secure way for SPA's to integrate with Spring Authorization Server. At the same time, we need to remain diligent on maintaining a tight security posture. We'll be presenting a webinar next week on this topic so I would encourage you to attend as we will answer a lot of your questions there. You can register here. |
Thanks for the detailed response @jgrandja, I completely understand your position. This balance between risk and convenience to our users is something we are evaluating in our project. Thank you for the link the webinar, I look forward to it. |
Thanks @bschoenmaeckers for the references! I can certainly see how people may interpret parts of the spec differently because it is written generically in some sections and is left up to the implementor to choose the appropriate strategy for implementation. The spec states:
This MAY be interpreted....if the client does not have any credentials assigned then it DOES NOT require authentication on the token endpoint. This most definitely will not be a default configuration that Spring Authorization Server will provide out-of-the-box. I hope the reasons are obvious. NOTE: Client Authentication can be customized via
We have considered the implications of adding support for refresh tokens for public clients alongside refresh token rotation but this scenario will increase the potential attack surface as detailed in this comment. |
@aberasarte Okta's refresh token support for public clients is very much similar to Auth0's implementation. However, it also supports the Please see my comments for Auth0 as the same points would apply for Okta. |
@aberasarte Microsoft's refresh token support for public clients has some security gaps - refresh token rotation is not implemented.
Although refresh token TTL is shorter than typical, re-authentication is still required after 24 hrs, which comes back to the main issue that SPA's face in the not so great user experience. |
@jgrandja great webinar yesterday guys, very well explained, especially this whole debate with refresh tokens on public clients. |
Thanks for the feedback @nickmelis and I'm glad you found it informative 👍
Everything we presented yesterday is fully documented in the OAuth 2.0 for Browser-Based Apps BCP and I've provided a summary of it in gh-297. The sender-constrained method will be implemented in gh-101 and documented at that point. There will be some challenges getting it working for a browser-based app but I'll figure something out. Please track that issue for progress. We're planning on adding the BFF sample to this repository soon as well. @sjohnr Can you provide some references for "silent renew"? |
I found these resources useful in my research:
3.1.2.3. Authorization Server Authenticates End-User states:
While there is some additional support for OIDC still missing, this flow does work out of the box even though (to my knowledge) the prompt parameter is not yet supported. I believe this is the case because, as I demonstrated in the webinar, you have a session and a saved consent available for the currently logged in user. |
Hello @jgrandja, following up on the silent renew mentioned in the webinar, our authenticator app returns |
@nickmelis I'm glad to have someone interested in discussing this feature, because it seems really close to being able to replace refresh tokens in a browser.
Are you saying the server that serves up your SPA is returning this header? Or what is the authenticator app?
In this case both your SPA and Authorization Server would have to be on the same domain, right?
While it goes against the OWASP recommendation of using However, I'm curious why you're seeing it fail when This is interesting enough that perhaps we should open a separate issue to investigate silent refresh/renew/authentication and have a dedicated discussion around it. If you're interested, please feel free open a new issue describing your challenge with the |
As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps-07#section-8 (#297) refresh token for public clients must be issuing with reduced time to live based on current refresh token time to live and duration of disuse.
@jgrandja, did I correctly understand and implement best practice?