Subject: [OAUTH-WG] Recommendations for OAuth 2.0 with Browser-Based Apps

From: Emond Papegaaij <[email-masked]>
Date: Mon, 06 May 2019 21:42:17 +0200

Hi all,

For a browser-based app, we try to follow the recommendations set in draft-
ietf-oauth-browser-based-apps-01. This does allow us to create a secure OAuth 
2.0 browser-based application, but at the moment it comes at a cost wrt. user 
experience when the access token expires. Our current solution forces us to 
redirect the user to the authorization server for a new authorization code. 
This will destroy most state the browser-based app has, causing the user to 
loose data. We are looking for a way to get a new access token in a secure way 
without disrupting the user.

As a refresh token is not issued to the app (as it should be), the application 
is forced to do a front-channel re-authentication for an authorization code. 
We are thinking of letting this front-channel communication happen in a hidden 
iframe. Naturally, this can only be done if no user interaction is required, 
hence we want to use the OIDC prompt=none. Is this a viable way of doing this 
re-authentication? Can it hurt to open up our authorization server for non-
interactive authorization requests inside an iframe? At the moment we do not 
allow iframes at all.

Maybe anybody knows a different way of achieving this? As I cannot believe we 
are the only ones facing this issue, maybe a recommendation can be put in the 
spec?

Best regards,
Emond Papegaaij

---

From: David Waite <[email-masked]>
Date: Mon, 6 May 2019 14:42:09 -0600

> interactive authorization requests inside an iframe? At the moment we do not 

Some AS implementations will block authentication in an iframe, but will allow you to use the OIDC prompt=none. This is already used quite often today by implicit apps. It is possible that AS implementations may allow iframes in the future, by detecting the frame is not covered with any buttons, and having the authentication be based on phishing-resistant authentication methods like W3C Web Authentication.

You could also trigger re-authorization with a user click, thus allowing opening the AS in a new window or tab. Once back on the site via callback, the temporary/pop-up window can do things like exchange the code for an access token, persist it, postMessage the original window, do window.close, etc.

The iframe and pop-up methods together can be used in lieu of persisting state across a redirect to the ISP. Many apps after reaching a sufficient level of complexity just wind up persisting the page state in some combination of local and remote storage, however. Javascript state is very brittle and will be broken by things as simple as a page refresh.

Native apps which opened the system browser had at least the capability of this problem as well - the application could be unloaded from memory/quit between when authentication started and ended.

On the other hand, refresh tokens IMHO are given quite a bit more fear in browser apps than warranted. It really depends on the AS - whether it can tie refresh tokens to the user’s authentication, or if they are tied to a long-term / persisted / "offline” authorization independent of an active user authentication. Currently, the latter is more common in implementations, and doesn’t make sense for browser applications. This doesn’t mean refresh tokens are automatically discounted for all environments.

Given the choice between an 8 hour access token, or a 10 minute access token and a refresh token that will expire at a maximum of 8 hours, the second provides quite a few more options to be more secure. (eg. checking backing user session and revocation, checking for updates to client blacklist, the rotation of the access token, rotating refresh tokens to prevent use by more than one client, expiring access on inactivity based on lag in refreshing, and so on).

If the refresh token is tied to the AS concept of user session, then it mostly replaces the ‘hidden iframe’ use above - you’ll only have your refresh token expire when the AS is asking for user presence on the front channel, presumably for interaction. Although, I suppose in some environments there could be a non-interactive reauthentication/factor as well (such as kerberos, MTLS, or re-verifying user location via geoip) where a hidden iframe might still provide UX benefit.

Browser based apps are significantly more vulnerable to code injection attacks than native apps (although don’t believe native apps are immune), so it may make sense for an AS to have a stricter default policy for browser-based applications than they would have for a native app. It also could make sense to allow for more scopes or longer-lived tokens for an audited, first-party browser-based application. Restrictions may be opened up even more for applications/browsers which also use PoP methods to prevent key exfiltration.

OAuth is nice in that the AS consolidates those responsibilities, however the flip side of that is that a client developer is really dependent on an AS to   provide a combination of features juggling good security and user experience.

> are the only ones facing this issue, maybe a recommendation can be put in the 

I think so far it has been an omission since this isn’t a “new” problem - implicit has the same issues and a subset of techniques (the difference bineg the capability to use refresh tokens now). However, it makes sense to document current practices in a BCP document :-)

WRT refresh tokens: I’m at the start of an independent blog post on considering token policies and lifetimes, but I expect it to take some time to polish and to get through reviewer feedback before publishing.

-DW

---

From: Emond Papegaaij <[email-masked]>
Date: Wed, 08 May 2019 09:37:56 +0200

Hi,

Thanks for your detailed reply. I've replied inline.

On maandag 6 mei 2019 22:42:09 CEST David Waite wrote:
> > ietf-oauth-browser-based-apps-01. This does allow us to create a secure
> > OAuth 2.0 browser-based application, but at the moment it comes at a cost
> > wrt. user experience when the access token expires. Our current solution
> > forces us to redirect the user to the authorization server for a new
> > authorization code. This will destroy most state the browser-based app
> > has, causing the user to loose data. We are looking for a way to get a
> > new access token in a secure way without disrupting the user.
> > As a refresh token is not issued to the app (as it should be), the
> > application is forced to do a front-channel re-authentication for an
> > authorization code. We are thinking of letting this front-channel
> > communication happen in a hidden iframe. Naturally, this can only be done
> > if no user interaction is required, hence we want to use the OIDC
> > prompt=none. Is this a viable way of doing this re-authentication? Can it
> > hurt to open up our authorization server for non- interactive
> > authorization requests inside an iframe? At the moment we do not allow
> > iframes at all.
> 
> Some AS implementations will block authentication in an iframe, but will
> allow you to use the OIDC prompt=none. This is already used quite often
> today by implicit apps. It is possible that AS implementations may allow
> iframes in the future, by detecting the frame is not covered with any
> buttons, and having the authentication be based on phishing-resistant
> authentication methods like W3C Web Authentication.

Fortunately for us, we build the AS as well, so we are in control here. It is 
a bit unfortunate that this does depend on OIDC. Doing user interaction in an 
iframe seems like a snake pit I'd rather not go into. I think it's also bad 
wrt the user interaction. We do not want user to enter their credentials on 
any site that looks like our AS. Users should only enter their credentials 
when the URL matches or AS.

> You could also trigger re-authorization with a user click, thus allowing
> opening the AS in a new window or tab. Once back on the site via callback,
> the temporary/pop-up window can do things like exchange the code for an
> access token, persist it, postMessage the original window, do window.close,
> etc.

This would work, but would really be a nuisance to the user. Especially with a 
token timeout of just one hour. Also, most of the times there would be no 
interaction, the user would just have to click a button. As a user I wouldn't 
understand why I have to do that all the time.

> On the other hand, refresh tokens IMHO are given quite a bit more fear in
> browser apps than warranted. It really depends on the AS - whether it can
> tie refresh tokens to the user’s authentication, or if they are tied to a
> long-term / persisted / "offline” authorization independent of an active
> user authentication. Currently, the latter is more common in
> implementations, and doesn’t make sense for browser applications. This
> doesn’t mean refresh tokens are automatically discounted for all
> environments.

How would you tie a refresh token to a user session on the AS? This would 
depend on the user visiting the AS on a regular basis and using a logout 
button when done. Most people simply close their browser when they're done, 
leaving the session open. Also, when making a call to the token endpoint to 
refresh the access token, there is no way of knowing that this call is 
actually initiated by the user, because it's done on a back channel. Perhaps 
this can be solved with DPOP with a keypair per browser, but this would really 
complicate the whole solution.

> Given the choice between an 8 hour access token, or a 10 minute access token
> and a refresh token that will expire at a maximum of 8 hours, the second
> provides quite a few more options to be more secure. (eg. checking backing
> user session and revocation, checking for updates to client blacklist, the
> rotation of the access token, rotating refresh tokens to prevent use by
> more than one client, expiring access on inactivity based on lag in
> refreshing, and so on).

I agree with you on this, but the spec currently states clearly that the AS 
should not issue refresh tokens to an SPA. Do you think this should be changed 
to something like: Authorization servers SHOULD NOT issue *offline* refresh 
tokens to browser-based applications. A refresh token should be tied to a user 
session on the AS.

> If the refresh token is tied to the AS concept of user session, then it
> mostly replaces the ‘hidden iframe’ use above - you’ll only have your
> refresh token expire when the AS is asking for user presence on the front
> channel, presumably for interaction. Although, I suppose in some
> environments there could be a non-interactive reauthentication/factor as
> well (such as kerberos, MTLS, or re-verifying user location via geoip)
> where a hidden iframe might still provide UX benefit.

In our case or AS might have to federate the authentication to some other AS, 
that would only work in an iframe. Therefore, I think we will go for the OIDC 
prompt=none in a hidden iframe. I'm not sure what to do if the AS reports that 
interaction is required, but at least the majority of the cases will be 
covered.

Best regards,
Emond Papegaaij

---

From: Thomas Broyer <[email-masked]>
Date: Wed, 8 May 2019 11:35:14 +0200

> interaction is required, but at least the majority of the cases will be

I've implemented OpenID Connect Session Management in two AS and one app
(not a SPA though); Session Management uses prompt=none in a hidden iframe.
When the AS redirects back with an error (login_required,
interaction_required, etc.) the hidden iframe can communicate the error to
the app (parent window), which then can display a message with a
button/link to reauthenticate in a popup.
prompt=none in a hidden iframe, plus interactions in a popup, look to me
like the way to go (my use-case has always been authentication though,
never authorizations alone, so maybe things would be different in your
case).

---

From: David Waite <[email-masked]>
Date: Wed, 8 May 2019 14:55:47 -0600

> On May 8, 2019, at 1:37 AM, Emond Papegaaij <[email-masked]> wrote:
> 
>> On May 6, 2019, at 1:42 PM, Emond Papegaaij <[email-masked]> 
> 

You do have this for native apps as well, however: app-auth-sso-ios-11-blog.png <https://www.pingidentity.com/content/dam/pic/images/managed/app-auth-sso-ios-11-blog.png>

> complicate the whole solution.

Yes, there are still no standard solutions for session keep-alive. There’s also not AFAIK a clear solution by the browsers on how to do an implicit logout on browser closed, now that browsers may persist sessions cookies.

I did pitch something about session keep-alive two years ago around this as part of DTVA (see https://bitbucket.org/openid/connect/src/f76ffe99c47d4698bc2995c32dc7a402dd6e8c47/distributed-token-validity-api.txt <https://bitbucket.org/openid/connect/src/f76ffe99c47d4698bc2995c32dc7a402dd6e8c47/distributed-token-validity-api.txt> ), which unfortunately didn’t go anyplace. For pure API apps participating in a session keep-alive system, a separate “user activity present” API to periodically poke is probably the best way to go. For managed devices running enterprise applications, you can just have a screen lock rather than tracking session activity at all.

For handling browsers in shared user environments which lack non-persistent cookies, you typically have to rely on session keep-alive/inactivity timeout and logout (which in OAuth would map to a token revocation)
 
> tokens to browser-based applications. A refresh token should be tied to a user 

I would like this language changed as well. It is complex due to so little existing token lifetime/policy guidance to reference. Previous conversations went a bit circular IMHO because of a lack of ground rules.

-DW

---

From: Emond Papegaaij <[email-masked]>
Date: Thu, 09 May 2019 10:19:38 +0200

On woensdag 8 mei 2019 22:55:47 CEST David Waite wrote:
> > actually initiated by the user, because it's done on a back channel.
> > Perhaps this can be solved with DPOP with a keypair per browser, but this
> > would really complicate the whole solution.
> 
> Yes, there are still no standard solutions for session keep-alive. There’s
> also not AFAIK a clear solution by the browsers on how to do an implicit
> logout on browser closed, now that browsers may persist sessions cookies.
> 
> I did pitch something about session keep-alive two years ago around this as
> part of DTVA (see
> https://bitbucket.org/openid/connect/src/f76ffe99c47d4698bc2995c32dc7a402dd
> 6e8c47/distributed-token-validity-api.txt
> <https://bitbucket.org/openid/connect/src/f76ffe99c47d4698bc2995c32dc7a402d
> d6e8c47/distributed-token-validity-api.txt> ), which unfortunately didn’t go
> anyplace. For pure API apps participating in a session keep-alive system, a
> separate “user activity present” API to periodically poke is probably the
> best way to go. For managed devices running enterprise applications, you
> can just have a screen lock rather than tracking session activity at all.

This spec seems to make it possible to keep an active session. However, it is 
bound to OIDC, which will not work in our case. Many of the application we are 
IdP for use SAML. The spec relies on all parties communicating session 
activity. For now, OIDC session management seems to be the best way to go.

> >> Given the choice between an 8 hour access token, or a 10 minute access
> >> token and a refresh token that will expire at a maximum of 8 hours, the
> >> second provides quite a few more options to be more secure. (eg.
> >> checking backing user session and revocation, checking for updates to
> >> client blacklist, the rotation of the access token, rotating refresh
> >> tokens to prevent use by more than one client, expiring access on
> >> inactivity based on lag in refreshing, and so on).
> > 
> > should not issue refresh tokens to an SPA. Do you think this should be
> > changed to something like: Authorization servers SHOULD NOT issue
> > *offline* refresh tokens to browser-based applications. A refresh token
> > should be tied to a user session on the AS.
> 
> I would like this language changed as well. It is complex due to so little
> existing token lifetime/policy guidance to reference. Previous
> conversations went a bit circular IMHO because of a lack of ground rules.

I can understand. This also highly depend on your use case. Something that 
might work for one application might be totally inappropriate for another.

Best regards,
Emond Papegaaij