Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OAuth2 one-time grant for signup and lost password

We're in the process of building a REST service that primarily is consumed by a JS frontend. The primary means for authentication is OAuth2.

We use the password grant type for logins, but one thing we're running into is: how do we design the "lost password" feature. Typically this is done by allowing a user to enter their email address and then sending them a one-time token.

This requires the following:

  1. An endpoint that is not authenticated. Something like POST /lost-password and accepts the email address. I realize this is not restful, but I can live with that. This endpoint then sends an email to the user with the one-time token.
  2. After the user uses the token to get back to the frontend, I basically want to allow the client to exchange that token for an OAuth2 bearer token.

So I didn't really see a grant_type in the spec that really does this. My question is:

Is this a situation where I should extend OAuth2 and create a new grant_type (extension is supported by OAuth2) that is specific for exchanging 1-time authentication tokens with an OAUth2 access token?

like image 592
Evert Avatar asked Oct 29 '22 16:10

Evert


1 Answers

TL;DR I would think twice on going with the creation of a new grant_type extension and instead would consider the use of existing flows and OpenID Connect in order to replace the usage of ROPC with the implicit grant flow which would allow you to get access tokens based on an already existing authentication session and in this way saving the user to re-enter their credentials.

Boring theory

That's a situation that falls outside the scope of OAuth 2.0; the whole subject of user authentication could have been considered out of scope if it was not for the inclusion of the resource owner password credentials grant (ROPC) which does seem to muddy the waters in terms of what's in and out of the specification.

However, you should consider that ROPC was introduced as a quick migration path for applications that were storing users passwords in order to function while the user was offline and not as a way to address the subject of user authentication.

Not even OpenID Connect which clearly focus on user authentication goes on to specify reset password mechanics as this is specific to one type of user credentials. This is generally left as an implementation detail for the identity provider.

A look at alternatives

Option A simple to implement

I'm assuming that you considered, but want to avoid the following option (A) which would not require any exchange of tokens and/or custom grant types:

  1. User requests password reset;
  2. Identity provider sends a password reset code URL through email;
  3. User accesses URL and changes password;
  4. User logins into the application (ROPC) using new password and obtains an access token;

This would work with your current approach without having to extend or touch anything related to OAuth2; however it would imply the user would have to reenter their credentials after actually completing the reset procedure password.

Option B it may be more elegant

Another option (B) more complex, but still without having to extend the protocols and also reinforcing what you mentioned about the /lost-password not being very REST'y. You can think of the endpoint to request and then actually process the password reset mechanism as being part of your identity provider and not your resource server (REST API).

You could then leverage this identity provider session in every OAuth2 flow that requires user authentication.

Option B.1

When user access your application you perform an implicit grant authentication request (according to OpenID Connect rules) using prompt=none.

If this succeeds, the user already had a session on the identity provider and you get a token. This session would be created when the user changes their password.

If this fails, you show the user your current login interface and use ROPC to get the token.

Option B.2

You dump ROPC and start using implicit grant for everything. This requires that you move the authentication user interface out of your client application and into your identity provider and then need to handle the specifics of a redirect-based flow instead of the more direct request/response nature of ROPC.

With this setup, both login and password reset operations would create the notion of an authentication session that could then be leveraged by your client application as a way to get tokens without bothering the user - through the implicit grant.

Final notes

Personally I think there are some advantages on having a more full-fledged identity provider; additionally, the implicit grant with prompt=none support also seems a very nice solution for the problem of how to refresh tokens in SPAs.

While SPAs cannot use refresh tokens, they can take advantage of other mechanics that provide the same function. A workaround to improve user experience is to use prompt=none when you invoke the /authorize endpoint. This will not display the login dialog or the consent dialog. In addition to that if you call /authorize from a hidden iframe and extract the new access token from the parent frame, then the user will not see the redirects happening.

(source: Auth0 - Which OAuth 2.0 flow should I use?; emphasis is mine)

like image 95
João Angelo Avatar answered Nov 09 '22 13:11

João Angelo