manfredsteyer / angular-oauth2-oidc

Support for OAuth 2 and OpenId Connect (OIDC) in Angular.
MIT License
1.86k stars 681 forks source link

Why offline_access is required to get a refresh token #1241

Open luisalves00 opened 2 years ago

luisalves00 commented 2 years ago

From the lib documentation it suggests that offline_access is required to have a silent refresh.

Latest recommendation:

Auth0 recommends using refresh token rotation which provides a secure method for using refresh tokens in SPAs while providing end-users with seamless access to resources without the disruption in UX caused by browser privacy technology like ITP.

And it's possible to get an a refresh token without a secret:

curl --location --request POST 'https://auth.group.eu/auth/realms/group/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'client_id=some-spa' \
--data-urlencode 'refresh_token=ey2v...frYTY'

Also it return a new refresh token (rotation - https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps-04#page-10).

So why offline_access is required for the silent refresh? Or Am I wrong?

jeroenheijmans commented 2 years ago

The offline_access scope is the standardized way to ask for refresh tokens. See the relevant OpenID Connect Core spec. The fact that these English terms don't really have a clear relationship has always confused me as well. Historical reasons, I guess?

Refresh token rotation means that every time you use a refresh token to get a new access token, the server also sends another new refresh token (and invalidates the old one).

As far as I know all this is not specific to this library, but if you find there's a specific issue perhaps you can elaborate a bit on that?

Hope that helps!

luisalves00 commented 2 years ago

It's possible to get refresh tokens from refresh tokens without needing offline_access scope. This is used in special scenarios where the app run background operation when you are not even using the application. Normal refresh tokens usually last hours (like session) instead of days. From the docs (https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/refreshing-a-token.html):

Please also note, that you have to request the offline_access scope to get a refresh token.

While on other lib (https://nice-hill-002425310.azurestaticapps.net/docs/documentation/silent-renew#silent-renew-code-flow-with-pkce-using-refresh-tokens):

Some servers require the offline_access scope for this to work which is defined in the OIDC specifications. Other servers do not require this.

The question is: If the offline_access scope is really required and why?
Tried to do an Auth Code with PKCE (without offline_access scope as it's optional) on Keycloak (maybe it's ahead of time) and it returns a normal refresh token that can be used on the curl that I've already provided to obtain a newly refresh token and the previous is invalidated (rotation).

Also this thread might help understand the question: https://stackoverflow.com/questions/49290819/why-are-refresh-tokens-considered-insecure-for-an-spa.

jeroenheijmans commented 2 years ago

Your curl example uses grant_type=refresh_token where you already need to have a pre-existing refresh token. It is indeed also used in this library when you are refreshing:

https://github.com/manfredsteyer/angular-oauth2-oidc/blob/d95d7da788e2c1390346c66de62dc31f10d2b852/projects/lib/src/oauth-service.ts#L921-L939

But to get the initial refresh token in a browser using Auth Code + PKCE (or with the deprecated Implicit or Password) flow, you have to ask for offline_access on the initial login.

So "yes", it is really required to ask for offline_access if you want to use refresh tokens down the line.

Whether it's secure enough depends on your situation. Plenty of people use refresh tokens (usually with mitigations like rotation and properly chosen expirations, as well as replay detection), and plenty of people avoid them at all costs (which requires iframe-based silent refreshes or a redirect-flash upon starting your app each time). Alternatively you can forego browser-based OpenID (and forego this library in the process) and choose a "BFF" (Backend For Frontend) setup instead.


As for KeyCloak, I'm not very experienced with it, but a quick search through their relevant documentation suggests that offline_access is similarly required to get the initial refresh token. They link to the exact same part of the RFC I also linked.

luisalves00 commented 2 years ago

Doing an Auth Code + PKCE on postman against Keycloak (12.0.3) I receive all tokens (access, refresh and id) without any offline_access scope, as is currently not even allowed on the server. So again I'm not convinced that offline_access is really required. From docs you provided:

offline_access OPTIONAL. This scope value requests that an OAuth 2.0 Refresh Token be issued that can be used to obtain an Access Token that grants access to the End-User's UserInfo Endpoint even when the End-User is not present (not logged in).

So it should be optional for specific use cases where the "End-User is not present (not logged in)". I think this last part is the most important as (I might be wrong) even after logout (end current session) the offline_access token is still valid as the application still need to perform operations after the user is not there. The user needs to revoke the offline_access on his account page.

after logout all sessions: image

I still have the offline_access grant: image

P.S: I don't see anything stating the offline_access is required in Keycloak's documentation link you sent. It also points to https://www.keycloak.org/docs/9.0/server_admin/#_offline-access (recommend reading) where:

Offline access is a feature described in OpenID Connect specification . The idea is that during login, your client application will request an Offline token instead of a classic Refresh token. The application can save this offline token in a database or on disk and can use it later even if user is logged out. This is useful if your application needs to do some "offline" actions on behalf of user even when the user is not online. An example is a periodic backup of some data every night.

The difference between a classic Refresh token and an Offline token is, that an offline token will never expire by default and is not subject of the SSO Session Idle timeout and SSO Session Max lifespan. The offline token is valid even after a user logout or server restart.

Which again makes me wonder why the angular lib forces the user to have offline_access even if the application doesn't needs it.

jeroenheijmans commented 2 years ago

Interesting! I had always thought offline_access was mandatory for getting the initial refresh token. But I suppose I might've been wrong there then (along with the docs for this library)?

Was it only the documentation that threw you off? Put differently: have you tried this library with your KeyCloak server, without the offline_access scope, relying on it sending a refresh token anyways?

luisalves00 commented 2 years ago

In fact, it was a co-worker that is using your library that spotted the problem as in one of the environments we had the offline_access scope disabled. I'm not a front-end developer so I cannot assure if the lib works fine or not without the offline_access. I did all the tests using postman/curl. I think you should review and test this to be sure and update the documentation accordingly. I still not sure if all Authorization Servers work as Keycloak, i.e. if they send the refresh_token on Auth Code with PKCE.

jeroenheijmans commented 2 years ago

Ahhhh hehe ok I had not realized you had not used the library at all yet. I think indeed someone from the community should have a go at this then and report back what they found. Thanks for the tip!

luisalves00 commented 2 years ago

Glad to help. Thanks.

landon-buttars-wgu commented 1 year ago

When reading this https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess A refresh token can be used without the offline_access scope while the access token is valid. If the access token is invalid or expired the refresh token will not be accepted to renew the access token.

The use of Refresh Tokens is not exclusive to the offline_access use case. The Authorization Server MAY grant Refresh Tokens in other contexts that are beyond the scope of this specification.

Refresh tokens are returned with access tokens in most cases to allow renewals when paired with a valid access token.

Hope this helps!

kikkauz commented 3 months ago

The scope offline_access is not a requirement to obtain a refresh_token, I could not find this in the openid specs or in the oauth specs. As already reported above, "OPTIONAL. This scope value requests that an OAuth 2.0 Refresh Token be issued that can be used to obtain an Access Token that grants access to the End-User's UserInfo Endpoint even when the End-User is not present (not logged in)." I'm wondering if it could not be a potential security issue in the context of an SPA.