I have a webapp that manages authorization and user roles via supertokens. When a session is initialized the app reads user role from database and passes it to supertokens role initialization.
Some users are admins and they may change the roles of other users. When the role of another user is changed I would like to revoke their active sessions, or change their role. This needs to take place immediately, even if the user has active sessions, so changing their roles in my database is not enough.
I know that supertokens have an open issue about "Define DB schema and APIs for UserRoles". Yet, I would expect that there would be some way to revoke active sessions of other users with their current structure.
Any help or explanation about how this might be approached will be appreciated.
First, some info about how the sessions for SuperTokens works:
An access token is issue which contains the payload (which has the user's role you added). The access token's verification is stateless (by default).
A refresh token is issued which is used to get a new access & refresh token when the existing access token expires.
Now, when you change a user's role, you want to propagate that change to all of their sessions. The backend SDK provided by SuperTokens has functions for that. For example, if you are using NodeJS, you can do something like:
let sessionHandles = await Session.getAllSessionHandlesForUser(userId);
// we update all the session's Access Token payloads for this user
sessionHandles.forEach(async (handle) => {
let currAccessTokenPayload = (await Session.getSessionInformation(handle)).accessTokenPayload;
await Session.updateAccessTokenPayload(handle,
{ role: "newRole", ...currAccessTokenPayload }
);
})
Since we are changing the contents of the access token of another session, that session will only know about this after it has refreshed. So by default, there will be a delay in the propagation of this role change for that user.
So here are your options for solving this issue:
Reduce the access token lifetime. This way, sessions are refreshed more often and the propagation of role change can happen more quickly.
Enable access token blacklisting. This can be done via the core's config.yaml setting (access_token_blacklisting: true flag). If you are using docker, you can pass ACCESS_TOKEN_BLACKLISTING=true. Through this setting, each session verification will query the core and if there is a change in the access token's payload, that will get reflected immediately. The downside to this is that session verification will not be slower due to extra network calls.
Finally, there are hybrid approaches that you can implement yourself:
Maintain a cache (on your own) of changes to roles and during session verification, add a middleware (after verifySession runs) to check your cache. If the role for the current user has changed, return a 401 to the frontend forcing it to refresh the session resulting in the access token's payload being updated immediately.
Hope this helps!
Related
I am using supertokens for authetication. Upon changing another user's permissions, I revoke his session which will cause his role to be updated after his current active token runs it's lifetime limit.
This causes the other user to be logged off at that point.
I would like his role to be updated (i.e. his session re created), but without logging him out and asking him again for his credentials.
Is that possible?
I will answer this question assuming that you are using the NodeJS SDK. If not, the answer overall still applies, but the function's name will change.
What you want is possible. I assume that you are strong that user's role in the access token, so instead of revoking the other user's session, you should use the updateAccessTokenPayload function from the SDK:
import Session from "supertokens-node/recipe/session";
async function updateRoleOfOtherUser(userId: string) {
// we first get all the sessionHandles (string[]) for a user
let sessionHandles = await Session.getAllSessionHandlesForUser(userId);
// we update all the session's Access Token payloads for this user
sessionHandles.forEach(async (handle) => {
let currAccessTokenPayload = (await Session.getSessionInformation(handle)).accessTokenPayload;
await Session.updateAccessTokenPayload(handle,
{ role: "newRole", ...currAccessTokenPayload }
);
})
}
The update the role will take into affect when their session refreshes, and they won't be logged out.
If you want the update to take affect immediately, you can maintain a cache on your side marking all the session handles that need to be refreshed early. On successful verification of of any such session that contains those session handles, you can send a 401 to the frontend forcing a session refresh and causing an update in their role.
We want to restrict users to multiple login sessions at a time, there should be a single active login session.
Users should be allowed to be logged in to one application from only one browser at a time. When the user log in the server should check his current active sessions to the same application from other browsers. If there is then log out from everywhere else and keep only the newest session.
ASP.Net Core's default authentication cookie middleware has a handy hook (via the CookieAuthenticationOptions.SessionStore property and ITicketStore interface) to allow you to implement custom backend storage for the cookie payload (claims). The end result is a protected cookie containing just basic AuthenticationProperties values and the session ID as a claim and everything else stored in the DB, keyed off the user ID and session ID etc.
With this in place you can automatically invalidate any existing session for a given user account (the ID of which being an indexed field/property in your backing store) by deleting or otherwise expiring any other sessions.
This also has the advantage of allowing you to invalidate sessions based on other circumstances like a password or other security settings changing.
You could also implement something to trigger back channel logout calls to client applications if you also track which clients they've signed into in the given session in the backing store too.
Note: The SessionStore property is a singleton concurrently accessed instance so ensure your implementation handles database connectivity appropriately if you go this route. Note also that the wireup is best done via an implementation of IPostConfigureOptions<CookieAuthenticationOptions>
Change the security stamp SecurityStamp.AbpUsers when the user logins.
The previous logins becomes invalid.
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/4821#issuecomment-524732321
Situation
Let's say an administrator of a site removes a user from the Admin role and adds her to the Contributor role. According to the site's database, that user has been demoted and should no longer have access to Admin-only features. Now the user comes back to the site some time after that change, but had logged in sometime before the change and is still logged in. So long as that user does not log out, she will continue to have claims that say she is in the Admin role. If she logs out, or gets logged out, she loses the claim that she belongs to the Admin role and when she signs back in receives the new claim of belonging to the Contributor role.
Desire
What I would like to happen, perhaps the next time the user requests a page from the site after the administrator made the change, is have that user transparently lose the Admin role claim and gain the Contributor role claim without them having to sign out or do anything special. In fact, I would prefer they are unaware of the change, except that her menu has changed a little because she can no longer perform Admin-only activities.
How would you handle this situation in a way that is invisible to the affected user?
My thoughts
I am using ASP.NET MVC 5 and ASP.NET Identity, but it seems like a solution to this could be easily generalized to other claims based frameworks that utilize cookies. I believe that ASP.NET Identity stores claims in the user's cookies by default in MVC 5 apps.
I have read the following post along with many others on SO and it comes closest to answering this question but it only addresses the case where the user updates herself, not when someone else like an administrator makes the change to her account: MVC 5 current claims autorization and updating claims
There is a feature in Identity 2.0 which addresses this, basically you will be able to do something like this which adds validation at the cookie layer which will reject users who's credentials have changed so they are forced to relogin/get a new cookie. Removing a role should trigger this validation (note that it only does this validation check after the validationInterval has passed, so the cookie will still be valid for that smaller timespan.
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider {
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
I am trying to implement a Remember me feature in a ServiceStack-based project. I don't want to use Basic Authentication because it requires storing password in clear text in a browser cookie, so I need to come up with an alternative approach that will be easy to maintain and customized to my existing database.
I understand that ServiceStack's own support for Remember me is based on caching the IAuthSession instance in the server-side cache, which by default is an in-memory data structure that is wiped out when the website restarts (not good). Alternatively, the cache can also be based on Redis or Memcached, which is better (cached data survives website restarts) but adds more moving parts to the picture than I care to add to it.
Instead, I would like to implement the this functionality using my own database:
Table Users:
UserID (auto-incremented identity)
Username
Password
Email
Name
etc...
Table Sessions:
SessionID (auto-incremented identity)
UserID (FK to Users)
StartDateTime
EndDateTime
SessionKey (GUID)
The way I see things working is this:
On login request, AuthService creates an empty instance of my UserAuthSession class (implements IAuthSession) and calls my custom credentials provider's TryAuthenticate method, which authenticates the user against the Users table, populates UserAuthSession with relevant user data and inserts a new record into the Session table.
Then the auth session is cached in the in-memory cache and ServiceStack session cookies (ss-id and ss-pid) are created and sent to the browser.
If the user checks Remember me then additionally my custom credential provider's OnAuthenticate method creates a permanent login cookie that contains the user's username and the auto-generated Sessions.SessionKey. This cookie will help us track the user on subsequent visits even if the auth session is no longer in the cache.
Now, suppose the site has been restarted, the cache is gone, so when our user returns to the site his auth session is nowhere to be found. The current logic in AuthenticateAttribute redirects the user back to the login screen, but instead I want to change the flow so as to to try to identify the user based on my custom login cookie, i.e.:
look up the latest Sessions record for the username extracted from the login cookie
check if its SessionKey matches the key in the login cookie
if they match, then:
read the user's data from the Users table
create my custom auth session instance, fill it with user data and cache it (just like at initial login)
insert a new Sessions record with a new SessionKey value
send back to the browser a new login cookie to be used next time
if the keys don't match then send the user back to the login screen.
Does the above logic make sense?
Has anyone already implemented anything similar using ServiceStack?
If I were to proceed with this approach, what is the best course of action that doesn't involve creating my own custom version of AuthenticateAttribute? I.e. which hooks can I use to build this using the existing ServiceStack code?
This is already built for you! Just use the OrmLiteCacheClient.
In your AppHost.Configure() method, add this:
var dbCacheClient = new OrmLiteCacheClient {
DbFactory = container.Resolve<IDbConnectionFactory>()
};
dbCacheClient.InitSchema();
container.Register<ICacheClient>(dbCacheClient);
I am not sure when this particular feature was added, perhaps it wasn't available when you originally asked. It's available in v4.0.31 at least.
i have to confess i've had this question for a very long time, never really understand.
say auth token is like a key to a safe, when it expires it's not usable anymore. now we're given a magic refresh token, which can be used to get another usable key, and another... until the magic key expires. so why not just set the expiration of the auth token as the same as refresh token? why bother at all?
what's the valid reason for it, maybe a historical one? really want to know. thanks
I was reading an article the other day by Taiseer Joudeh and I find it very useful he said:
In my own opinion there are three main benefits to use refresh tokens which they are:
Updating access token content: as you know the access tokens are self contained tokens, they contain all the claims (Information) about the authenticated user once they are generated, now if we issue a long lived token (1 month for example) for a user named “Alex” and enrolled him in role “Users” then this information get contained on the token which the Authorization server generated. If you decided later on (2 days after he obtained the token) to add him to the “Admin” role then there is no way to update this information contained in the token generated, you need to ask him to re-authenticate him self again so the Authorization server add this information to this newly generated access token, and this not feasible on most of the cases. You might not be able to reach users who obtained long lived access tokens. So to overcome this issue we need to issue short lived access tokens (30 minutes for example) and use the refresh token to obtain new access token, once you obtain the new access token, the Authorization Server will be able to add new claim for user “Alex” which assigns him to “Admin” role once the new access token being generated
Revoking access from authenticated users: Once the user obtains long lived access token he’ll be able to access the server resources as long as his access token is not expired, there is no standard way to revoke access tokens unless the Authorization Server implements custom logic which forces you to store generated access token in database and do database checks with each request. But with refresh tokens, a system admin can revoke access by simply deleting the refresh token identifier from the database so once the system requests new access token using the deleted refresh token, the Authorization Server will reject this request because the refresh token is no longer available (we’ll come into this with more details).
No need to store or ask for username and password: Using refresh tokens allows you to ask the user for his username and password only one time once he authenticates for the first time, then Authorization Server can issue very long lived refresh token (1 year for example) and the user will stay logged in all this period unless system admin tries to revoke the refresh token. You can think of this as a way to do offline access to server resources, this can be useful if you are building an API which will be consumed by front end application where it is not feasible to keep asking for username/password frequently.
I would like to add to this another perspective.
Stateless authentication without hitting the DB on each request
Let's suppose you want to create a stateless (no session) security mechanism that can do authentication of millions of users, without having to make a database call to do the authentication. With all the traffic your app is getting, saving a DB call on each request is worth a lot! And it needs to be stateless so it can be easily clustered and scaled up to hundreds or even thousands of servers.
With old-fashioned sessions, the user logs in, at which point we read their user info from the database. To avoid having to read it again and again we store it in a session (usually in memory or some clustered cache). We send the session ID to the client in a cookie, which is attached to all subsequent requests. On subsequent requests, we use the session ID to lookup the session, that in turn contains the user info.
Put the user info directly in the access token
But we don't want sessions. So instead of storing the user info in the session, let's just put it in an access token. We sign the token so no one can tamper with it and presto. We can authenticate requests without a session and without having to look up the user info from the DB for each request.
No session ... no way to ban users?
But not having a session has a big downside. What if this user is banned for example? In the old scenario we just remove his session. He then has to log in again, which he won't be able to do. Ban completed. But in the new scenario there is no session. So how can we ban him? We would have to ask him (very politely) to remove his access token. Check each incoming request against a ban list? Yes, would work, but now we again have to make that DB call we don't want.
Compromise with short-lived tokens
If we think it's acceptable that a user might still be able to use his account for, say, 10 minutes after being banned, we can create a situation that is a compromise between checking the DB every request and only on login. And that's where refresh tokens come in. They allow us to use a stateless mechanism with short-lived access tokens. We can't revoke these tokens as no database check is done for them. We only check their expiry date against the current time. But once they expire, the user will need to provide the refresh token to get a new access token. At this point we do check the DB and see that the user has been banned. So we deny the request for an access token and the ban takes effect.
The referenced answer (via #Anders) is helpful, It states:
In case of compromise, the time window it's valid for is limited, but
the tokens are used over SSL, so unlikely to be compromised.
I think the important part is that access tokens will often get logged (especially when used as a query parameter, which is helpful for JSONP), so it's best for them to be short-lived.
There are a few additional reasons, with large-scale implementations of OAuth 2.0 by service providers:
API servers can securely validate access tokens without DB lookups or RPC calls if it's okay to not worry about revocation. This can have strong performance benefits and lessen complexity for the API servers. Best if you're okay with a token revocation taking 30m-60m (or whatever the length of the access token is). Of course, the API servers could also keep an in-memory list of tokens revoked in the last hour too.
Since tokens can have multiple scopes with access to multiple different API services, having short-lived access tokens prevents a developer of API service for getting a lifelong access to a user's data on API service B. Compartmentalization is good for security.
Shortes possible answer:
Refresh tokens allow for scoped / different decay times of tokens. Actual resource tokens are short lived, while the refresh token can remain valid for years (mobile apps). This comes with better security (resource tokens don't have to be protected) and performance (only the refresh token API has to check validity against DB).
The following is an addition to the benefits of refresh tokens that are already mentioned.
Safety First!
Access tokens are short-lived. If someone steals an access token, he will have access to resources only until access token expires.
"...But what if a refresh token is stolen?"
If an attacker steals the refresh token, he can obtain an access token. For this reason, it it recommended that a new refresh token is issued each time a new access token is obtained. If the same refresh token is used twice, it probably means that the refresh token has been stolen.
When the refresh token changes after each use, if the authorization
server ever detects a refresh token was used twice, it means it has
likely been copied and is being used by an attacker, and the
authorization server can revoke all access tokens and refresh tokens
associated with it immediately.
https://www.oauth.com/oauth2-servers/making-authenticated-requests/refreshing-an-access-token/
Of course, this is just another layer of security. The attacker can still have time to obtain access tokens, until the refresh token is used a second time (either by the attacker or the real user).
Always keep in mind that the refresh token must be stored as securely as possible.