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

Implement refresh token rotated feature for public clients gh-297 #335

Closed
wants to merge 1 commit into from

Conversation

boggard
Copy link

@boggard boggard commented Jul 1, 2021

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?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 1, 2021
Copy link

@eugenelesnov eugenelesnov left a 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.

@jgrandja
Copy link
Collaborator

Thanks for the PR @boggard.

OAuth 2.0 for Browser-Based Apps documents the best practice for refresh tokens, however, it also states:

Authorization servers may choose whether or not to issue refresh tokens to browser-based applications

We're going to apply gh-296 for the 0.2.0 release as this eliminates the risk of a leaked refresh_token by a public client.

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.

@jgrandja jgrandja added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 23, 2021
@jgrandja
Copy link
Collaborator

jgrandja commented Jan 6, 2022

@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.

@jgrandja jgrandja closed this Jan 6, 2022
@jgrandja jgrandja added the status: declined A suggestion or change that we don't feel we should currently apply label Jan 6, 2022
@zhenchuan9r
Copy link

@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.

@zhenchuan9r
Copy link

A better solution would be to make it configurable, before you can fully implement gh-297.

@jgrandja
Copy link
Collaborator

@zhenchuan9r

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

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?

@chrisrhut
Copy link

reason(s) why this limits use cases for public clients

@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.

@zhenchuan9r
Copy link

zhenchuan9r commented Jan 13, 2022

@zhenchuan9r

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

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?

Thanks for reply, and my thoughs are already expressed by chrisrhut.
I agree that requiring end-users to re-authenticate just because access_token expires would indicate bad UE.
Actually I still don't quite understand the extra risk by allowing refresh_token to be issued.
Most of our sites are javascript sites, without refresh_token we may be forced to use session_id.
Even if we replace refresh_token by session_id, session_id can also live forever if stealed. Forbidding refresh_token seems not making any sense on improving security in this case.

@jgrandja
Copy link
Collaborator

Thanks for the feedback @chrisrhut @zhenchuan9r.

After we release 0.2.2 at the end of the month, I'll start looking into gh-297 and will log separate issues to start working on building more support for Public Clients.

@nickmelis
Copy link

Following, I'm also working with public clients and find a long lived access token not ideal in this case.

@jgrandja
Copy link
Collaborator

@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.

@aberasarte
Copy link

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:

  • Ask the user to login again frequently.
  • Increase the access_token expire time.

I guess that most developers will end up setting really long access_token expiration times and we all know that this is a bad idea and even more dangerous than leaking a refresh_token that is going to be rotated.

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.

@jgrandja
Copy link
Collaborator

Thanks for the feedback @aberasarte. We'll be looking into this shortly.

@jgrandja
Copy link
Collaborator

@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.

@chrisrhut
Copy link

@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):

As a prerequisite for the flow described below, the backend MUST have established a secure session with the user agent, so that all requests from that user agent toward the backend occur over HTTPS and carry a valid session artifact (such as a cookie) that the backend can validate.

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?

@jgrandja
Copy link
Collaborator

@chrisrhut Regarding your comment:

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)

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 authorization_code grant, without an authenticated session of the Resource Owner.

As a prerequisite for the flow described below, the backend MUST have established a secure session with the user agent, so that all requests from that user agent toward the backend occur over HTTPS and carry a valid session artifact (such as a cookie) that the backend can validate.

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.

@chrisrhut
Copy link

@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.

@nickmelis
Copy link

@jgrandja

Allowing Public Clients to obtain refresh tokens opens things up so this is not something we want to introduce.

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?

@jgrandja
Copy link
Collaborator

@chrisrhut

I didn't intend to derail this thread/issue and make it my personal technical support line

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.

@jgrandja
Copy link
Collaborator

@nickmelis

Does that mean the option of issuing access and refresh tokens to public clients will never find its way into spring-authorization-server?

A public client is able to obtain an access token using authorization_code with PKCE. This capability has been available for quite some time now and we don't plan on removing it.

As far as refresh_token capability for public clients go, there are no plans to add this capability. See "Refresh Tokens for Public Clients" in gh-297 for additional details.

@rayman245
Copy link

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.

@aberasarte
Copy link

Hi @jgrandja,

As others already said, thanks for your thorough explanation about the refresh_token in public clients and for taking into account our opinion. I still think that not having this, makes the flow pretty much unfeasible to use and will push developers to set really long expiration access tokens which IMHO is even more dangerous. I guess that this is the reason why all of the Authz servers I know, support refreshing tokens when using the PKCE flow: Google, Microsoft, Hydra, Okta...

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 👌 !

@nickmelis
Copy link

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).

@jgrandja
Copy link
Collaborator

@rayman245

I think it would be bit forceful to make users stop using the 'Remember Me' functionality on front end clients

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.

@jgrandja
Copy link
Collaborator

jgrandja commented Feb 25, 2022

@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 refresh_token grant? The client MUST authenticate at the token endpoint and since public clients are not assigned credentials, how do they authenticate with the provider to assert their identity?

@rayman245
Copy link

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.

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.

@jrmcdonald
Copy link

@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.

@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

@nickmelis
Copy link

@aberasarte
Copy link

@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.

Sure!

Exchange authorization code for refresh and access tokens...

Refresh tokens for web apps and native apps don't have specified lifetimes. Typically, the lifetimes of refresh tokens are relatively long...

The code verifier, only known to the legitimate application, is transmitted when the authorization code is exchanged for access and refresh tokens...

... Refresh token rotation works with SPAs, native apps, and web apps in Okta.

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 4, 2022

@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:

On a first authentication attempt using the password grant type

the purpose of the refresh token is to obtain a new valid access token or just revoke the previous one

To receive a new access token using the refresh_token grant type, the user no longer needs to enter their credentials, but only the client id, secret and of course the refresh token.

This article does not apply to the flow being discussed in this ticket. The flow in the article is password grant type and it's using a confidential client NOT public client, since the client_secret is being passed during the refresh_token grant flow.

Also we have built apps with google sign-in using this guide -> https://developers.google.com/identity/protocols/oauth2/native-app

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 (client_secret required) NOT a Public Client.

The article OAuth 2.0 for Client-side Web Applications is meant for SPA's, however it references usage of the implicit grant, which is deprecated.

@rayman245
Copy link

@jgrandja

@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.

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.

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 (client_secret required) NOT a Public Client.

Also according to OAuth mobile apps are considered public clients please refer to -> https://oauth.net/2/client-types/
And yes, the only option for mobile app integration now is treating them as confidential client, which would be unwise, since its easy for anyone to reverse engineer apk/ipa files.

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 4, 2022

@aberasarte The Google reference you provided:

Google https://developers.google.com/identity/protocols/oauth2/native-app#exchange-authorization-code.

... uses a Confidential Client NOT a Public Client when authenticating on the refresh_token grant flow.

I provided more details in this comment:

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 (client_secret required) NOT a Public Client.

The article OAuth 2.0 for Client-side Web Applications is meant for SPA's, however it references usage of the implicit grant, which is deprecated.

@bschoenmaeckers
Copy link

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).

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 4, 2022

@bschoenmaeckers

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 refresh_token grant flow if they do not have credentials (client_secret) assigned? The token endpoint REQUIRES client authentication.

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.

@bschoenmaeckers
Copy link

Can you please provide the exact reference in the relevant spec that states this?

RFC6749 - Section 9 (Native Applications)

Native applications that use the authorization code grant type
SHOULD do so without using client credentials, due to the native
application's inability to keep client credentials confidential.

RFC6749 - Section 10.1 (Client Authentication)

The authorization server MUST NOT issue client passwords or other
client credentials to native application or user-agent-based
application clients for the purpose of client authentication. The
authorization server MAY issue a client password or other credentials
for a specific installation of a native application client on a
specific device.

When client authentication is not possible, the authorization server
SHOULD employ other means to validate the client's identity -- for
example, by requiring the registration of the client redirection URI
or enlisting the resource owner to confirm identity. A valid
redirection URI is not sufficient to verify the client's identity
when asking for resource owner authorization but can be used to
prevent delivering credentials to a counterfeit client after
obtaining resource owner authorization.

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.

The authorization server must consider the security implications of
interacting with unauthenticated clients and take measures to limit
the potential exposure of other credentials (e.g., refresh tokens)
issued to such clients.

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)

If the client type is confidential or the client was issued client
credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.

This is also true when using a refresh token: RFC6749 - Section 6 (Refreshing an Access Token)

RFC6749 - Section 6 (Refreshing an Access Token

Because refresh tokens are typically long-lasting credentials used to
request additional access tokens, the refresh token is bound to the
client to which it was issued. If the client type is confidential or
the client was issued client credentials (or assigned other
authentication requirements), the client MUST authenticate with the
authorization server as described in Section 3.2.1.

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 4, 2022

@jrmcdonald @aberasarte @nickmelis

Auth0 does in fact support refresh tokens for public clients.

Key points in the Refresh Tokens docs:

The refresh token behaviour is applicable to OIDC-conformant applications

For native applications, refresh tokens improve the authentication experience significantly. The user has to authenticate only once, through the web authentication process. Subsequent re-authentication can take place without user interaction, using the refresh token.

If you want to allow users to get refresh tokens while offline, you can select the Allow Offline Access switch in API Settings.

You can increase security by using refresh token rotation which issues a new refresh token and invalidates the predecessor token with each request made to Auth0 for a new access token. Rotating the refresh token reduces the risk of a compromised refresh token.

Key points in the Get Refresh Tokens docs:

To get a refresh token, you must include the offline_access scope when you initiate an authentication request through the /authorize endpoint. Be sure to initiate Offline Access in your API. For more information, read API Settings.

If you are requesting a Refresh Token for a mobile app using the corresponding Native Client (which is public), then you don't need to send the client_secret in the request since it's only required for confidential applications.

Key points in the Refresh Token Rotation docs:

Refresh tokens are often used in native applications on mobile devices in conjunction with short-lived access tokens to provide seamless UX without having to issue long-lived access tokens

With refresh token rotation enabled in the Auth0 Dashboard, every time an application exchanges a refresh token to get a new access token, a new refresh token is also returned. Therefore, you no longer have a long-lived refresh token that, if compromised, could provide illegitimate access to resources. As refresh tokens are continually exchanged and invalidated, the threat is reduced.

Unfortunately, long-lived refresh tokens are not suitable for SPAs because there is no persistent storage mechanism in a browser that can assure access by the intended application only. As there are vulnerabilities that can be exploited to obtain these high-value artifacts and grant malicious actors access to protected resources, using refresh tokens in SPAs has been strongly discouraged

Based on my analysis, Auth0 supports refresh tokens for public clients based on the following conditions:

  1. The application/client must perform the OpenID Connect 1.0 Authentication using the Authorization Code Flow (with PKCE). NOTE: The standard OAuth 2.0 Authorization Code Grant would not apply.

  2. The offline_access scope must be passed in the Authentication Request.

  3. Refresh Token Rotation SHOULD be enabled to help mitigate refresh token leakage.

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 refresh_token grant flow WITHOUT needing to authenticate on the token endpoint. This is NOT an implementation behaviour we would provide as a default configuration.

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.

@jrmcdonald
Copy link

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.

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 4, 2022

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:

If the client type is confidential or the client was issued client
credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.

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 OAuth2AuthorizationServerConfigurer.clientAuthentication()

The authorization server must consider the security implications of
interacting with unauthenticated clients and take measures to limit
the potential exposure of other credentials (e.g., refresh tokens)
issued to such clients.

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.

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 4, 2022

@aberasarte Okta's refresh token support for public clients is very much similar to Auth0's implementation. However, it also supports the prompt parameter in the Authentication Request.

Please see my comments for Auth0 as the same points would apply for Okta.

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 4, 2022

@aberasarte Microsoft's refresh token support for public clients has some security gaps - refresh token rotation is not implemented.

For refresh tokens sent to a redirect URI registered as spa, the refresh token expires after 24 hours. Additional refresh tokens acquired using the initial refresh token carries over that expiration time, so apps must be prepared to re-run the authorization code flow using an interactive authentication to get a new refresh token every 24 hours. Users do not have to enter their credentials, and usually don't even see any user experience, just a reload of your application. The browser must visit the login page in a top level frame in order to see the login session. This is due to privacy features in browsers that block third party cookies.

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.

@nickmelis
Copy link

@jgrandja great webinar yesterday guys, very well explained, especially this whole debate with refresh tokens on public clients.
At this point it would be good to be able to put together some documentation on all available options for public clients as of today (BFF, silent refresh, mutual TLS, etc) so that everyone can weigh in the different options with pros and cons. Is that in the pipeline? I saw there's quite a few documentation tickets already open here on GitHub.
Personally we'll try to investigate the silent refresh mechanism explained yesterday as it seems the less intrusive option for us right now. Any other suggestion is obviously much appreciated.
Thanks again!

@jgrandja
Copy link
Collaborator

jgrandja commented Mar 11, 2022

Thanks for the feedback @nickmelis and I'm glad you found it informative 👍

it would be good to be able to put together some documentation on all available options for public clients as of today (BFF, silent refresh, mutual TLS, etc)

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"?

@sjohnr
Copy link
Member

sjohnr commented Mar 15, 2022

I found these resources useful in my research:

3.1.2.3. Authorization Server Authenticates End-User states:

The Authorization Server MUST NOT interact with the End-User in the following case:

  • The Authentication Request contains the prompt parameter with the value none. In this case, the Authorization Server MUST return an error if an End-User is not already Authenticated or could not be silently Authenticated.

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.

@nickmelis
Copy link

Hello @jgrandja, following up on the silent renew mentioned in the webinar, our authenticator app returns X-Frame-Options=DENY by default, which prevents implementing silent renew in an iframe. What immediately comes to my mind is to set the header to SAMEORIGIN. Have you got any opinion/security consideration around that? Thanks

@sjohnr
Copy link
Member

sjohnr commented Mar 24, 2022

@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.

our authenticator app returns X-Frame-Options=DENY by default

Are you saying the server that serves up your SPA is returning this header? Or what is the authenticator app?

What immediately comes to my mind is to set the header to SAMEORIGIN.

In this case both your SPA and Authorization Server would have to be on the same domain, right?

Have you got any opinion/security consideration around that?

While it goes against the OWASP recommendation of using DENY, I think it would be plausible if everything is on the same domain, and not overly risky.

However, I'm curious why you're seeing it fail when X-Frame-Options=DENY is used, as Spring Authorization Server is based on Spring Security and should be sending that header as well. We only see it fail in Safari (for example). Auth0 has an article explaining that this is because of ITP (Intelligent Tracking Prevention) which is blocking the third party cookies, not the <iframe>.

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 DENY header in detail and we can continue the conversation there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.