I have a scenario where the refresh token fetch periodically fails. Keycloak is the oidc provider, using code flow from a web app. When the access token has expired, the back-end application reaches out to the keycloak token endpoint for a new one using the refresh token - generally, this works. Intermittently, however, the refresh token fails, with "invalid_grant, Token is not active". From everything I've found on SO and elsewhere this is due to clock drift. It's certainly possible, this is all running in containers on VMs on systems I do not control, and I would like to mitigate for the situation.
The system in place actually fails pretty gracefully - just not with an XHR. So if I navigate between logical pages in my single page application, and this scenario occurs, the user does not see anything amiss. When a background fetch happens the user sees an error, but the next request succeeds - here's the sequence of requests that causes it:
/<original-request-endpoint> 302, invalid token, redirect to keycloak auth endpoint for the realm for the user to sign in. The redirect is my logic: if the user is not authenticated, send them to the login page.
/keycloak/...realm/.../auth 302 - the user's session is actually still valid, redirect to my authorization endpoint on the back end with a code.
/authorize 302 - this gets a new access token from the keycloak token endpoint and sets the cookie, so future requests succeed with the new token. Redirect to the landing page.
/landing this is effectively ignored.
I was thinking of trying to duplicate the keycloak auth request on the back end if the token refresh fails - but I don't like that idea, it feels messy, I'll need to reconstruct all the request context (cookies etc.) for this special scenario.
Any idea what I'm doing wrong or a better approach to handling refresh token failure?
Related
I have an ASP.net MVC web application that uses Microsoft's Owin middleware (Microsoft.Owin.Security.OpenIdConnect) to configure OpenID Connect authentication. My identity provider (Okta) is configured to support refresh tokens and I have confirmed that it is working. When signing in, my application receives an Access, ID and Refresh Token as expected. These tokens are validated and returned to the client in a cookie called ".AspNet.Cookies" (the default). On each request, the cookie and these tokens are parsed into a set of claims. Great so far. 👍
The Owin (Katana) middleware does not appear to do anything further with the Refresh Token, so I have implemented a token client to request a new Access Token from my IdP using the Refresh Token. This is working as expected. 👍
Two questions:
When and where should the application check to see if the Access Token is expired and request a new one?
After receiving new a access, id, and refresh token, how and where should the application update the user identity, claims and cookie?
OWIN COOKIE UPDATES
I believe the comment at the end of this post has the type of code you can write - I remember using similar code a few years back.
With OWIN you are using a server side stack secured by cookies so I'm not sure where access tokens are actually used, but maybe one of these is true?
The C# back end uses tokens to call an API
The Web UI downloads tokens from the web back end and makes Ajax calls to an API
PATTERN FOR HANDLING EXPIRED TOKENS
The only reliable pattern to handle expiry is to do this in the API client code:
When you get a 401 response from the API
Try to refresh the token and retry the API call with a new access token
If you can't refresh the token, redirect the user to sign in again
I always implement this with 2 classes, as in this SPA code of mine:
ApiClient - handles API calls
Authenticator - handles OAuth calls
If the Web UI is getting tokens from the web back end and then calling an API, your web back end could provide MVC operations similar to those in my authenticator class:
getAccessToken - get the current access token, though it may fail with a 401
refreshAccessToken - use this if a 401 is received and you need a new token
TOKEN EXPIRY TIMES
It is also possible to check token expiry times in the background - to reduce the number of client 401s. This is not a full solution however, since 401s can occur for other reasons in addition to expiry.
On all OAuth2 documentations i looked, including the Oauth2 spec, the Oauth2 Authorization Code flow ends in the point where the server side holds both the refresh and access tokens.
As a server-side of a web app, what I wish is to not finish the flow here, but response my web user agent with the access\refresh tokens
This in order to have my user agent later make calls to my app's server-side APIs using the access token for auth (the app's server-side will verify the token against the authorization server).
The implicit flow might be the flow i should have been using here (right?), but the authorization server in my company supports only the authorization code flow (and not the implicit flow) for now.
I thought of adding a redirect url as a query param inside of the redirect url of the authorization API.
This way, when the authorization flow will ends, and my app's server side will get the tokens, it will redirect back to the User agent's url, providing the access\refresh tokens as hash fragments
Is it safe for the server-side to redirect to user agent with the tokens this way?
Is there any better practice for redirect back to the user agent?
I am building a (set of) web application; the backend has REST-like API, the frontend will be some REST JS app, android apps etc; and I'm trying to come up with an SSO functionality.
Looking at Oauth2/OIDC it seems the best way would be to use Implicit flow; however, the access tokens in implicit flow (in oidc) have a set expiration. The refresh token is not part of implicit flow.
How do I ensure that the user will stay logged in? I.e. when the access token expires, the frontend application will try to obtain a new one from an auth server; that is supposed to ask for username/password. Alternatively, it can build a session with the frontend (using cookies), but how is that different from a refresh token?
It seems to me that getting the access token e.g. from the android app means at least opening the web browser; depending on the expiry length, that could be quite often. Is that the correct flow or am I missing something?
You are right, the issuance of a refresh token is not allowed with the Implicit grant type.
However, the refresh token and the access token are not needed to know if the user is logged in or not (the access token only allows you to access on protected resources). You have to use the ID Token which is issued in the authorization response.
You can verify if the user is still logged in by sending an authorization request with the query parameter prompt=none (see section 3.1.2.1. Authentication Request). I recommend you to send the current ID Token using the id_token_hint query parameter as mentioned in the same section:
ID Token previously issued by the Authorization Server being passed as a hint about the End-User's current or past authenticated session with the Client. If the End-User identified by the ID Token is logged in or is logged in by the request, then the Authorization Server returns a positive response; otherwise, it SHOULD return an error, such as login_required. When possible, an id_token_hint SHOULD be present when prompt=none is used
If you receive an error (login_required or interaction_required) then the user may be logged out.
Another way could be to use the Session Management feature. But as this specification is not yet approved (draft 27), it may be subject to changes and may not be available. However it is a very simple way to know the status of the user.
On the rfc https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-12#page-40 , you can see a sentence saying "Removed 'expired_token' error code."
That is, "invalid_token" will be used for expired token cases.
Why was that? I think it was very useful for the client to distinguish an expired token and an invalid token. Without "expired_token", how can the client decide whether to refresh an access token?
It was removed to prevent potential security leaks of information that attackers might use.
Let's say an attacker can get a copy of your Access Token by sniffing all of your network traffic. But the attacker only analyzes this information after your token has expired. If the attacker tries to make requests to your service with that token, and receives back an 'expired_token' code, they know that they are onto the right thing, and just need to get a later copy.
If the attacker, however, receives back an 'invalid_token' message, it makes it more difficult for the attacker to understand that the data they had WAS an access token (maybe it was something else?).
Furthermore, the way access / refresh tokens typically work, is something like this:
A client needs to make an API request and has an access token and a refresh token.
The client makes a request to the API service with their access token and gets an 'invalid_token' message.
The client locally validates their refresh token, sees it is still valid.
The client then submits a refresh request using their refresh token, and gets a new access token.
If the client locally validates their fresh token above, and gets an error (maybe the token expired, or something), they know they need to re-authenticate the user FRESH to receive both a new refresh AND access token.
The same is true if, when the user submits their refresh request, their refresh token is considered invalid (maybe the server-side revoked this token?).
Say I'm developing an Evernote or YouTube client, and after the user logs in, the app gets an authorization token from the API server.
Then I use this token to interact with the server until, sometime later, the token expires.
The key is, I don't know if this token is expired until the server returns an error with a message like 'token expired'. Then I have to fetch a new token. Maybe it occurs while the user is posting a message.
So what is an elegant way to deal with this scenario? I want to combine fetching a new token with continuing the last request, so that the user just feels it works as usual.
It depends on which flow you're using. But in general, if you are able to refresh the auth token (via refresh token) without redirecting the user for credentials again, you should do it seamlessly.
Also, you should know exactly when a token is going to expire. If you're coding against an OAuth 2 implementation, the auth token response should give you an 'expires_in' field as a time span telling you exactly how long the token will expire after it was issued to you.