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:
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.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?
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.
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.
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:
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.
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.
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.
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With