Here’s what I’m facing. Currently, refresh tokens are stored in httpOnly cookie, and on every SSR, we refresh the token and send the whole response to the browser, the access token is only saved in memory, then the browser will continue for renewing it. The problem arises when we have 2 tabs, the access tokens will go out of sync. The initial tab’s access token will be unusable.
To work around this, we can either use localStorage to sync the tokens or use non-httpOnly cookie to store the access tokens. Or, manage tokens server-side, But both would have a problem, let’s say Tab 1 sent a request using the current access token, then I open Tab 2, the token is refreshed, Tab 1 request just reached the API, and the token is already invalidated.
I can’t find a proper solution unless we build more server-side logic into NextJS. Or perhaps if we don’t invalidate access token when refreshing and let it dies off the expiry. That way, we have ample time to fight the race condition.
Or, we retry all requests using exponential back off. Actually this kinda solves everything, and there’s a package ready for it. Except we’ll need to rewrite many parts to adopt the new library, and also there would be false alarms in the logs.
Or, we just ignore this and hope the race condition won’t appear, although it seems to me it’s going to occur pretty easily.
We are facing this problem in the company I work for.
Our solution is to store the refresh token in localStorage. If the token is regenerated, the other open tabs would detect a change in localStorage, retrieve the stored token (which now is a brand new one, generated by some other tab) and from that point on use it. The token is stored in memory until another change in localStorage is triggered.
To learn how to detect a change in storage: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
Honestly, there is no standard and robust solution to that as far as I know, and based on my research on the Internet.
Interesting problem. In single page apps each tab would be independent and have its own access token that it could store in HTML5 session storage or memory.
In server side web apps cookies are usually shared across all tabs and can cause conflicts.
I would try to follow the SPA model - there is no good reason why using your app on tab 2 should invalidate the token on tab 1.
They are different sessions and should be independent - as they would be if running one tab in Chrome and the other in Firefox.
Keep access tokens short lived (<= 60 minutes) so that after logout on tab 1 the user does not stay logged in on tab 2 for long.
Related
I have a React SPA which makes API calls to a .NET backend. I am currently using JWT authentication. I have implemented the access/refresh token 'pattern'. My problem is that when the page is refreshed, I have to have my tokens stored down somewhere so that, on refresh, the user does not have to log in again to get new tokens. Having spent a fair bit of time researching this issue, it is not clear what the best way of getting around this is.
I have been told that you should not store them down to local storage because they become vulnerable to XSS attacks. A solution which I have come across a few times is to store the tokens in a HTTP-only cookie. Ok - but then I do not see why you don't just use a HTTP-only cookie instead of a refresh token. That is, on log-in, user is issued with relatively long expiry HTTP-only cookie and relatively short expiry access token. On page-refresh, cookie authentication is used to get new access token (if still valid, otherwise user logs in again). I don't mind if, on page refresh, an API call must be made to retrieve a new access token (I just don't want to trigger a user log-in).
I don't understand why a HTTP-only cookie and refresh token might be required, unless it's just to provide another layer of security.
How does one monitor the access token for expiry and refresh it in an Office-Addin Single Sign on Solution?
MSAL Single Sign On in an Office-Add in is a complicated beast. The access token is obtained by calling:
OfficeRuntime.auth.getAccessToken()
The access token is then swapped for an access token for my API using the Microsoft Identity On-Behalf-Of-Flow. All good.
But access tokens expire. The OfficeRunetime.auth does not appear to have anything that manages the expiry and refresh of access tokens. Unlike its counterpart MsalService (which runs in a normal Javascript/Angular web app - not an Office-Addin) and manages the tokens - and can not be used in an Office-Addin.
Do I simply continue to call OfficeRuntime.auth.getAccessToken() in an HTTP Interceptor every time prior to an API call? I understand that getAccessToken() does first consult the cache for an AccessToken, do I infer then that it is managing the expiry of that token and going back to identity server to get a new one?
Or am I completely on the wrong track?
After a lot of reading and prototyping, I'll answer my own question.
Firstly, I am a beginner when it comes to MSAL, tokens, claims, scopes etc and all matters authentication, and I've painfully waddled my way through it all. I welcome all feedback to this answer, and will update the answer if necessary
Here is what I have observed: But firstly, in conclusion to my question
OfficeRuntime.auth.getAccessToken() manages and refreshes the token when it expires (for MSExcel).
Background
Firstly, I am using MSExcel. The Microsoft Documentation seems to
group Excel/Word/PowerPoint together and deals with Outlook
differently. I am grateful for #Eugene Astafiev answer to this
question from the Outlook perspective, but I found the opposite to
be true for Excel
The office Add-in OfficeRuntime.auth.getAccessToken() returns,
what Microsoft docs calls, a 'bootstrap' token being an access token that also contains an identity token. The bootstrap token can be used in the On-Behalf-Of Flow (in which it is called the 'assertion' token) to swap it for other access tokens for different scopes. In my app, I swap if for an access token for my API, lets call it the APIAccessToken
Both the bootstrap token and the identity token within it expire at
the same time, after about 87 minutes. The expiry time is recorded
on the Identity Tokens 'exp' claim. I don't know if this is
configurable somewhere, but the key is that both expire together.
The APIAccessToken (the one returned from the OBO flow) also expires at the same time as the bootstrap token (well almost, it seems to expire randomly up to 10 mins after its official expiry time). Every time I call the OBO flow, the generated APIAccesstoken returned is different- but only in the later half of it. Although they are different, and without any regard to what time they were generated, they all still expire at the same time as the bootstrap token (assertion token) that was used to be swapped for them, and they all remain valid, and useable until the expiry time (even though they are different)
It is not possible to get the APIAccessToken in any other way in the Excel Addin than using the OBO Flow. This is because of the challenges with IFrames and login redirects in Office Excel. Excel provides the getAccessToken() and a dialog box that runs in its own instance.(see MS Excel docs on Single Sign In for Office-Addin) The access token is only useful for OBO flow
The fallback authentication strategy outlined in the MS Docs https://learn.microsoft.com/en-us/office/dev/add-ins/develop/sso-in-office-add-ins is absolutely necessary, as you will always fallback when the user has not granted consent for your SPA and your API to access their profile. However, as currently documented it is not sufficient. It does not get consent for MSOffice15, and this leads to ongoing login problems. I have raised a Stack Overflow question on this here Office-Addin Single Sign In, How to manually add MSOffice15 consent and Error 13005 and currently have implemented a work around (I'll post it in response at that link). The fallback must manage three consents. MSOffice15, Your SPA and Your API.
Refreshing The Token
1. Every call to OfficeRuntime.auth.getAccessToken() returns the same bootstrap token with the same expiry time until it expires. When it expires I confirm that this function then calls the Identity Server Endpoint and obtains a new bootstrap token, with a new Identity token, whose expiry time is again ~ 87mins. This makes sense, since the Microsoft Documentation states that it is stored in Cache, and is retrieved by OfficeRuntime.auth.getAccessToken(). This makes this function call light-weight and can be invoked often.
MySolution
I derived a new class (inherited) from the official MSALInterceptor class. On every call to a protected resource I call the OfficeRuntime.auth.getAccessToken() If the token's expiry date has changed then I know that a new bootstrap token has been issued. I then call some middleware code to get a new APIAccessToken
I hope this saves someone some time! And leaving on a great note, I have finally got the entire Single Sign On login solution working for an Office-Addin SPA (angular) that manages all consents (office, SPA and my API). Took over 8 weeks work. Now its Friday 3pm - and I'm sneaking past the boss and going home :-)
I've faced with the same problem recently too. You (user) must restart Outlook to get a new access token (not expired). Office JavaScript API doesn't provide any method or property for that. The best what you could do is to handle exceptions and notify users to restart the host.
The cache is an in-memory cache. It is only cleared on reboot of Outlook. The tokens will automatically time out after the TTL for the token has expired, but can depend on the server implementation.
You can post or vote for an existing feature request on Tech Community where they are considered when the Office dev team goes through the planning process.
I'm trying to auto logout an app if user logged in from another tab or browser.
I'm using react-idle-timer and Parse Server.
I'm not sure if this line of code will be useful
Parse.Error.INVALID_SESSION_TOKEN
I can access session token via localStorage
const res = await Parse.Cloud.run('login', values);
console.log(res.session);
You can't differentiate a request from a different tab within the same browser. It will share the same session token and be indistinguishable from any other request. Requests made from different browsers will have different session tokens and if they store some user info you can determine which ones belong to which user.
If we reframe your question it sounds like you only want to allow for one active login per user. This requires a centralized or clustered session management solution that supports preventing multiple concurrent sessions.
If you will only ever have one backend server or you are using a shared database to store sessions, then you can do this a bit easier depending on what you are using for session management since all sessions will be available on the one server.
It is not clear to me if your users are connecting directly to parse server. If they are you will most likely need to modify parse-servers code directly to support this.
In general this is a silly idea. Unless you have a legal requirement to do so I would not advocate worrying about this. Bypassing this is usually as easy as copying around a session token.
I know of two ways of storing and authenticating the user login info:
Storing the user id in a server side session and then when someone calls to the server check if they have a user session. (Using node client sessions)
When the user logins, store a authentication token in the user's table and store the token locally on the users client as well. Then when the user calls to the server they send the authentication token as a header and check if the token is in the user table.
While both of these ways are viable and applicable, I have problems/questions with both of them:
I've been told storing the info in session goes against the rest api idea of auto scalability. Is this true and is there a way around it?
When storing the authentication key, won't you only be able to store one key/instance per user. What would you do if you wanted to have the same account logged in on two computers or clients (I know I can just create an authentication table, but what if a client loses a token and the authentication token stays forever in the authentication table).
If there are better ways of doing this please bring it up, but I am very confused which direction to move towards. I am gravitating toward the second way, but I still like the first way.
Edit: I have narrowed it down to JWT and my second idea. I don't know which would be better with node.
How about JSON Web Tokens? They're a variant of the second method you mention and are a recognised industry standard, so you can easily find an implementation for your stack.
You can store the tokens in a key-value store like Redis instead of a relational database, which will be much faster. Redis also supports timing out a key after a while, so expired tokens will disappear automatically. You can also set it up so that each token is invalidated once used, and any request to the API returns a new token for use in the next request, allowing users to continually refresh their token.
Assuming you are using express, you can use express-session for managing your sessions.
Then, you need to add a suitable session store instead of the default MemoryStore, which is for debug use only, and will not scale to more than one process instance (for the reasons you mentioned in your question).
Fro example, if you are using a PostgreSQL database, you could consider using connect-pg-simple. This would store your sessions in your DB, so that your session management does not prevent you from scaling your node.js server. In addition, you can store multiple sessions per user, that will expire (and get automatically erased) based on the maxAge that you configure, thus solving the second problem you mentioned.
I am building an Single Page app using angular.js and I am facing this issue for which I am not able to find the right answer.
When we do a full page refresh in an angular app, how should we check if the user still has a valid session ?? State Provider or UI router merely routes the url to the requested page, but what if the session of the user has expired ?
One thing that comes to my mind is to use a service and store a Boolean value there once the user logs in and on every page refresh or state change, we check this Boolean and redirect the user to login page, if this value is false. But, if we do a refresh, this Boolean value is reset.
Thought of storing this key value in a cookie or html local storage, but how safe are these values getting stored here. Some one can reset the value of this Boolean to gain access to a page.
Please let me know.
To make a client-side app secure you will need to involve a server of some sort. Storing values in cookies or local storage will not do any good as these can be manipulated by a user (as can everything else on the browser).
Not sure what options you have available to you but I would recommend looking into Nodejs/Expressjs/Passportjs - this is a pretty awesome combo and very good support here on SO.
Once you make progress in this area you will then be in a position to ask a more focused question.
I think you're conflating a few concepts here.
One - is the user authentication to the server. It must be the server, otherwise the concept of a user session in a client-side-only app is useless. This is facilitated (typically) by an authentication cookie. The cookie is a security token given to the user and signed by something secret on the server. The cookie contains things like login name and expiration. The cookie is validated by the server on every request the browser makes.
Two - is the nice user experience maintained in the client-side app. What I mean by that, is that if you didn't check whether the cookie has expired, your ajax calls to the server would (and should) fail with HTTP 401 - Unauthorized. You likely would want to prevent that, and have your app preemptively redirect to a login page, or if applicable, request to refresh the security token.
So what does all of that mean?
Enforce authentication on the server
Create a loginService that checks session expiration from a cookie, or whatever else you use fo your user authentication.
Use resolve parameter of $routeProvider or $stateProvider to have the loginInfo available to controllers. Here's my answer on SO to a question you might find useful.