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.
Related
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!
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
I am creating an application and I am looking for a solution for user authentication (checking if the user is logged in, basically). I am reading online and it seems that many people recommend using a session store/table in your db (store roles, views etc..) vs. just storing the cookie id in the DB in that users column. My question is, what is the difference between storing this data in a "session" store, which is basically just another table and storing this data in your database alongside the other user data (username, passwordHash etc..). I understand that this is useful for data that may change when the user logs in and out again, but are there any advantages to having a session store if my applications state stays consistent across log ins. Thanks.
You need a way to store user data between HTTP requests and sessions helps you to do so.When a user visits our site, it creates a new session for the user and assigns them a cookie. Next time the user comes to the site , the cookie is checked and the session id which is stored in the cookie is retrieved and searched in the session store .Session store is a place where you store all your data regarding your session.So using a session store automates this method and it eases your work.So whenever someone pings your server it will add the session id of the user in your database. I will recommend foe you to look into JWT which is also a interesting way to do authentication.
Question: How can I enforce existing users to set up 2FA in .Net Core 3.1 Identity?
I have seen a couple of answers here already, but I have issues with them as follows:
Redirect user to set up 2FA page on login if they do not have it set up. Problem with this is that the user can simply jump to a different url to avoid this, therefore it is not actually enforced.
Have some on executing filter that checks if the user has 2FA enbaled or not and if not redirect them to MFA set up page. The issue I have with this is that on every single navigation the server must go to the database to check whether the user has this field enabled, thus creating a significant performance hit on each request. I know one trip to the database may not sound like much but I have worked with applications where this was the norm and other things used this method, causing a pile up of pre action db queries. I want to avoid this kind of behavior unless absolutely necessary.
My current idea is to on login:
Check the users credentials but NOT log them in
userManager.CheckPasswordAsync(....)
If the credentials pass, check if the user has 2FA enabled or not. If they do, continue through login flow, if not:
Generate a user token:
userManager.GenerateUserTokenAsync(.......)
and store this along with the username in a server side cache. Then pass a key to the cached items with a redirect to the 2FA setup page, which will not have the [authorize] attribute set, allowing users not logged in to access it.
Before doing anything on the 2FA set up page, retrieve the cached items with the provied key andverify the token and username:
userManager.VerifyUserTokenAsync(......)
If this doesn't pass, return Unauthorized otherwise continue and get the current user from the supplied UserName in the url that was passed via a cache key. Also dump the cached items and key so that should the url be snatched by a dodgy browser extension it can't be used again.
Continue to pass a new cache key to new user tokens and usernames to each 2FA page to authenticate the user as they navigate.
Is this an appropriate use of user tokens? And is this approach secure enough? I'm concerned that having the user not logged in presents security issues, but I think it is necessary in order to avoid the previously mention problem of going to the database on every request to check 2FA, as with this method trying to navigate away will just redirect to login.
I implemented this via a Filter Method
I have a BasePageModel which all my pages inherit
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (!User.Identity.IsAuthenticated)
{
await next.Invoke();
return;
}
var user = await UserManager.GetUserAsync(User);
var allowedPages = new List<string>
{
"Areas_Identity_Pages_Account_ConfirmEmail",
"Areas_Identity_Pages_Account_ConfirmEmailChange",
"Areas_Identity_Pages_Account_Logout",
"Areas_Identity_Pages_Account_Manage_EnableAuthenticator",
"Areas_Identity_Pages_Account_ResetPassword",
"Pages_AllowedPageX",
"Pages_AllowedPageY",
"Pages_Privacy"
};
var page = context.ActionDescriptor.PageTypeInfo.Name;
if (!user.TwoFactorEnabled && allowedPages.All(p => p != page))
{
context.Result = RedirectToPage("/Account/Manage/EnableAuthenticator", new { area = "Identity" });
}
else
{
await next.Invoke();
}
}
I then changed both the Disable2fa and ResetAuthenticator pages to redirect to the main 2fa page
public IActionResult OnGet() => RedirectToPage("./TwoFactorAuthentication");
And removed the reset/disable links from that page
I chose to implement a more modern and OAuth friendly solution (which is inline with .Net Core Identity).
Firstly, I created a custom claims principal factory that extends UserClaimsPrincipalFactory.
This allows us to add claims to the user when the runtime user object is built (I'm sorry I don't know the official name for this, but its the same thing as the User property you see on controllers).
In here I added a claim 'amr' (which is the standard name for authentication method as described in RFC 8176). That will either be set to pwd or mfa depending on whether they simply used a password or are set up with mfa.
Next, I added a custom authorize attribute that checks for this claim. If the claim is set to pwd, the authorization handler fails. This attribute is then set on all controllers that aren't to do with MFA, that way the user can still get in to set up MFA, but nothing else.
The only downside with this technique is the dev needs to remember to add that attribute to every non MFA controller, but aside from that, it works quite well as the claims are stored in the users' cookie (which isn't modifiable), so the performance hit is very small.
Hope this helps someone else, and this is what I read as a base for my solution:
https://damienbod.com/2019/12/16/force-asp-net-core-openid-connect-client-to-require-mfa/
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-5.0#force-aspnet-core-openid-connect-client-to-require-mfa
I am building a website using Node.JS/Express.JS that will allow a user to log in using a 3rd party provider (Discogs via OAuth1.0a).
I have successfully implemented the authentication process so that a user grants access to their Discogs account and I am returned an Access Token for future API calls. The Access Token does not expire. The user is classed by Discogs as an "authenticated application".
At the moment I am storing the Access Token in a session, which persists even when the user restarts the browser, or my server is restarted, so the user stays logged in. Great.
However, when I log the user out by destroying their session and they repeat the authentication process, the 3rd party provider treats the user as a newly authorised application, leaving the old authorised app behind. How can I get around this? Is it better to not destroy the user's session on log out and instead store the logged in state of the user? Discogs do not provide a method for de-authentication.
Also, there is some config to be set against a user once they are logged in. Should I created a dedicated DB table or equivalent for this, or would storing this in the session suffice? It seems like a dedicated user table may be superfluous as I am relying on the user's session id to identify them.
Generally, you will probably want to save some info about your users permanently on your own servers, so probably in a database.
In your specific case, that database should probably save some kind of unique user ID that you get from Discogs (do not save the access token itself for security reasons), which you can use on subsequent logins to identify which access tokens belong to the same user.
Your flow would probably be something like this:
User logs in via Discogs for the first time, you get an access token, put that in session
You figure out a unique user id somehow, you save that to your DB along with any other user info you might need
You put that ID in the session as well
User logs out, you destroy the session, but keep the info in your DB
User logs in via Discogs again, you get a different access token, put that in session
You figure out the unique user id, which matches the ID in your DB, so you write that ID into your session - now you can treat the user as the same user, just with a different access token
The unique user ID can be anything that is, you guessed it, unique. Might be an actual ID, a username or email address - I'm not familiar with Discogs but I'm sure you can figure something out and how to obtain it.