This is the comment that led me to ask this question.
I've got a server side Node.js app, using googleapis package. Users log in with their Google accounts, and I store tokens in their session. The credentials I get are as follows
{ access_token: '<AN ACCESS TOKEN>',
token_type: 'Bearer',
id_token: '<A LONG ID TOKEN>',
expiry_date: <A TIMESTAMP> } // why do some places say there's an expires_in instead of this
There's no refresh_token because the users have already logged in for the first time and clicked accept, and I didn't store the refresh token (looks like I should've).
So, when the expiry_date is reached, if the user tries to make a request for us to save something to their google drive, I get an error message:
{ [Error: invalid_request] code: 400 } // ...no further details
My 2-part question:
I assume I'm getting that error message because the access_token in my OAuth client object is expired (because the call works fine before the token expires). Is this correct? Why is the error message not more detailed?
In the linked answer at the top, the solution is to force the accept prompt again, get the refresh token, and store it permanently, and use it to get a new access token when it expires. Why is this a better option than just checking if the token is expired, and having a user reauthenticate when we want to make a call to the API? Which is the "correct" way to ensure that my logged in users can always make the drive API call to save their documents?
Right, the 400 response is because of the expired access token. I'm not sure why Google doesn't give more detail, but it's common for services to use the 400 status code to indicate some kind of credentials problem. The definition of the status code indicates that it's a client issue.
Both approaches will work and they each have advantages and disadvantages. The client-side re-authentication method you're suggesting has the advantage of making the implementation simpler, since you don't have to store the refresh token and don't have to implement the refresh process. The downside is that forcing the user to re-authenticate every hour is less user-friendly. At the least they will be redirected away from your app, and they may have to explicitly log in or re-authorize as well. You'll just have to look at the trade-offs and pick what works best for your use case.
Related
I'm trying to get access token by calling https://www.googleapis.com/oauth2/v4/token with
grant_type: 'refresh_token',
client_id: GOOGLE_OAUTH_CLIENT_ID,
client_secret: GOOGLE_OAUTH_CLIENT_SECRET,
refresh_token: refreshToken
I'm sure the clientId/secret are correct.
Since for some refresh tokens - I do get access token, for others I do get notification that the token is revoked, but in some cases I'm getting a
error: '400 - {"error":"invalid_grant","error_description":"Bad Request"}'
Since for some cases I do receive success/token revoked, I assume it eliminates NTP issue.
Any ideas what else could be wrong and where to look?
It may be that you have an invalid access token. This could be due to many causes,such as the user's account has been deactivated since the token was created or token being revoked or expired, Ensure that you are always using the newest refresh token.
Time is critical with regards to tokens, Ensure that you are in with Google NTP server. If necessary, sync your time with Google NTP. Also an incorrect/ incomplete refresh token will also result in an invalid grant. In order to request a refresh token you must first have requested offline access. Access tokens work for one hour, however it is a good idea to refresh them when there is five minutes left to avoid any issues with clock stew.
Requesting an access token every time you need to access the api may also result in invalid grant, for flooding the auth server. Google has made changes if a user changes their password refresh token that grants access to some scopes will be revoked. Here is a StackOverflow answer and a blog post that I found which explain some of the reasons this error can occur.
Also you may try changing from the https://www.googleapis.com/oauth2/v4/token URI to https://oauth2.googleapis.com/token. The previous URI should continue to work, but the later URI is the new default. See this github
Let's say we have short-lived access token (15 minutes) and long-term refresh token (7 days).
When should we ask backend to refresh access token?
I see two options:
After user logs in we start a countdown to automatically refresh token one minute before access token expires.
We don't implement timer and we try to refresh access token ONLY if we get 401 response from backend.
In first option I see one advantage -
if access token and refresh token will expired AND user stays on the page, not taking any action, he also doesn't send any http request than the timer still works and user is logged out automatically.
In second option -
if access token and refresh token will expired user will be logged out ONLY if he will make some action on page for example: leave a page or make a http request.
If he will stay on page he won't be logged out automatically.
What is a better implementation on frontend than?
I would recommend option 2 as your default behavior, since it will give you a resilient app. Every OAuth client should do this, since 401s can sometimes also be received for infrastructure reasons in some setups, eg token signing certificate renewal.
Option 1 is an optimization, if you want to reduce 401 responses from APIs. However it can lead to incorrectly developed clients and APIs if you are not careful. Personally I never use it.
Note that an expires_in field is returned with the access token but there is no equivalent field for the refresh token, so the client cannot detect when the user session will expire unless you develop a custom solution.
When coding API calls it is recommended to do this, as in this sample code of mine:
When a 401 is received try a token refresh
On success retry the API call - once only
On failure redirect the user to authenticate again
Out of interest there is an online version of the above app that allows you to test OAuth expiry events to see how this behaves - see my Quick Start page
I have recently started using json web tokens and I have a few unanswered questions
what would happen if a users token expires while they are online? would they be forced to log in again when they request the next protected route? and if so it feels like there has to be a way around this so that the user does not randomly get logged out
how would you log someone out before the jwts expiration date considering the client holds all the info
any clarification on those 2 questions would be appreciated
what would happen if a users token expires while they are online? would they be forced to log in again when they request the next protected route? and if so it feels like there has to be a way around this so that the user does not randomly get logged out
When you make a request in a webapp, you need to pull in the available token. So the answer is "what does your app do?". There are lots of things you can do, such as making the request with the invalid token, or no token at all. But obviously that's not really a good idea. The best strategy is to:
Before every API call needing a token, check if the current token is valid
If the token isn't valid ask your auth provider to get a new token. This is known as silent authentication. How the provider handles this is totally up to them, usually they have a HttpOnly cookie saved for a secure domain, and return a new JWT.
If the silent auth fails, then the provider will tell you to log the user in.
At this point the best course of action is to redirect to the user to the auth provider using the same "login strategy" they previously used, to get a new "session" and a new "token".
how would you log someone out before the jwts expiration date considering the client holds all the info
Logging out is completely separate from token expiration. Logging out from a user standpoint is:
Tell the auth provider to remove the HttpOnly secure cookie
Delete the JWT in the browser
Change the UI to display the user as no longer logged in.
But that doesn't expire the token. The token will always be valid until the exp date. If you want to also prevent that token being used as if it was valid, you must deny-list the token using your AuthZ IAM as a service. For instance, if you were using Authress to manage access control, a DELETE https://api.authress.io/v1/users/{userId}/tokens/{tokenId} would cause subsequent checks using that token to return Forbidden. However, I will stress, the token is still valid, but you've taken the step to mark it as Deny-listed.
would they be forced to log in again when they request the next protected route?
If there is no other information that associates the session with the logged-in user (for example, there aren't any cookies or other tokens the server can use), yes.
Yeah, it's not very user-friendly, and JWTs often have a short expiry time. A common solution to this issue is to provide a refresh token as well, which could be stored in a HttpOnly cookie:
A refresh token has 2 properties:
It can be used to make an API call (say, /refresh_token) to fetch a new JWT token before the previous JWT expires.
It can be safely persisted across sessions on the client!
This way, the client can always have a valid JWT at any one time; they won't have to log back in, or logout and then log back in.
how would you log someone out before the jwts expiration date considering the client holds all the info
You can implement logic on the server to somehow blacklist or ignore the JWT from a particular user until they log in again. For example, after parsing the JWT, you could check to see whether the server considers the token to still be usable or not. If not, return a 401 error.
Can we use the same access token to request another app resource or validate Token. What happens after 3599 seconds to an access_token? can we still use it?
How many times we can use the same refresh token? (Is there any way to restrict to one time if possible)
How to check if the existing access_token and refresh_token were valid ones or not?
Please help.
PAVANSAI C
Can we use the same access token to request another app resource or validate Token. What happens after 3599 seconds to an access_token?
can we still use it?
When you acquire an access token, it is only meant to be used against certain resources (you specify them when you request a token). You can't use that token for any other resources. Generally an access token is valid for an hour (3600 seconds) but that's configurable at Azure AD level. Once this time period expires, you can't use that token anymore as using it will throw an error.
How many times we can use the same refresh token? (Is there any way to restrict to one time if possible)
Similar to access token, there's also an absolute expiration for refresh token (it is usually 14 days). When you use a refresh token to get a new access token, you also get a new refresh token. You should be using the new refresh token instead of an old one.
How to check if the existing access_token and refresh_token were valid ones or not?
A successful response to an access token request will include the number of seconds the returned access token is valid for (expires_in), as well as the time at which the access token will expire (expires_on). Use these to keep track of whether the access token is still valid or not.
Note: You should consider using client libraries such as MSAL, which will do this automatically. Your code only needs to ask for a new token token, and the library will take care of figuring out if the last token received is still valid, or if a new one is needed.
Another possibly way to test it is try to use the access_token/refresh_token in an operation and catch the exception. Try to parse the exception to figure out what's wrong with the token. For example, if an access token has expired and you use it you will get an error telling you exactly that. That would be an indication for you to get a new access token using the refresh token. (This approach relies on the resource provider (i.e. the API) to return a message that clearly indicates that the token is expired, which is not always the case.)
What's wrong with my setup?
I am using django-allauth for social signup and recently i added contacts to it's scope. Things are working fine. It now asks for permission to manage contacts and I am able to get contact details of users through the API.
But once i make a request to get contacts of a user(I am not saving any refresh token or accss token at that time), after an hour when i make the request again with same token, It shows this error "Invalid token: Stateless token expired".
However I can still login into the website and the token does not change. However when I logout and login again the token changes and i can again get the contacts using that token for one hour.
What's the issue? What am I missing?
See, when you are logging into the website, you are probably using cookies. So basically you might be using the same session and actually the api is not called.
The time when you are logging in incognito mode or in a diffrent browser, that cookie cannot be used, so this time api is called. For this reason, the token is getting changed.
For example, if after few users have signed up with google, you change the scope of the app, what happens is, if the user has enabled cookies and it has not expired, when he visits your site, it simply logs him in. It does not asks for permissions (that you added recently to scope). But when he logs out and logs in again, then it asks for the additional permission and th token also gets changed.
What you should do is, you should go through th codes of django-allauth and clear it out how they are using the token. You must also know that for getting refresh token, you must have offline access enabled in your configuration.