How to update a claim when using Session Authentication Module (SAM) - wif

I'm using the Session Authentication Module to store the user claims in the authentication cookie.
What is the recommended approach to update the user's claims whilst they are logged in? An example would be if they update their profile (First Name/Last Name) and we want to update the associated claims.
I don't want to log the user out when this happens so DeleteSessionTokenCookie is not an option.

Set a new session cookie with SAM.WriteSessionTokenToCookie.

Leastprivilege's answer is correct.
But in response to Vijay V's comment, here's example code on how to update the cookie after adding new claims to your claims principal:
var sam = FederatedAuthentication.SessionAuthenticationModule;
if (sam != null)
{
var token = new SessionSecurityToken(claimsPrincipal);
sam.WriteSessionTokenToCookie(token);
}
Pulled the code from here.

Related

What exactly does Owin rememberBrowser do?

In several places in a standard ASP.Net MVC Identity 2.0 Owin implementation you'll see rememberBrowser, like:
await signInManager.SignInAsync(user, isPersistent: isPersistent, rememberBrowser: false);
If you do set rememberBrowser to true, I've noticed that I can kill the browser, kill IIS Express, delete the user the browser was logged in as, even restart my machine, and the browser is still treated as logged-in. Not so great, considering a deleted user being treated as authorized/logged-in is going to cause all sorts of issues in code behind the [Authorize] attribute that expects to have a valid user to work with.
So what is it exactly that rememberBrowser is doing, and is there any risk that someone could just fake rememberBrowser in their cookies to bypass OWIN login? It seems the point of [Authorize] is to guarantee no one but logged-in users access a given Controller Action, and rememberBrowser seems to be a hole in that guarantee.
Bonus question: Is there a way to disable rememberBrowser so that even if a forged cookie did come in, it would be rejected?
I think rememberBrowser is relevant only in Two-factor authentication. So if you set it to true, the browser will acquire TwoFactorRememberBrowser cookie which allow the user to skip 2FA authentication (if enabled) during the login process.
Is there a way to disable rememberBrowser so that even if a forged
cookie did come in, it would be rejected?
The cookie created from rememberBrowser is used in conjunction with the authentication cookie. It will only allow the user to skip 2FA, therefore it is useless without being authenticated first.
The answer by #Hezye is correct, but I'll elaborate on this a bit more.
Here is the code that creates an identity for "rememberBrowser" CreateTwoFactorRememberBrowserIdentity (https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Owin/Extensions/AuthenticationManagerExtensions.cs line 215):
public static ClaimsIdentity CreateTwoFactorRememberBrowserIdentity(this IAuthenticationManager manager,
string userId)
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
var rememberBrowserIdentity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId));
return rememberBrowserIdentity;
}
So this identity is with type of "TwoFactorRememberBrowserCookie" and with only claim of user ID.
Looking on the source code of SignInManager that uses this code: (https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Owin/SignInManager.cs line 106) :
if (rememberBrowser)
{
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity);
}
Here IAuthenticationManager is used to sign-in 2 identities: one for the actual user, another for "rememberBrowser". And I believe this will produce 2 cookies - one user authentication cookie, another remembering the browser.
In SignInManager when using SignInOrTwoFactor the code (line 218) checks if "RememberBrowser" identity is already set in the cookies.
OWIN cookies are protected by encryption, encryption is borrowed from DpapiDataProtector (documentation). I'm no expert in cryptography so can't comment on the strength of cryptography. I'm just saying that "rememberBrowser" cookie is encrypted the same way as the main authentication cookie.
Regarding your exercise where you restarted your IIS, machine, etc. Have you removed the cookies from the browser? Because if you have not, Identity (or rather OWIN) will treat browser as logged-in, even if the original user record is removed from the database. Though user will not be logged-in for long as there is code in the default template MVC that checks with the database for the user record and logs out if user record have been changed.
As for disabling "rememberBrowser" - always pass false to that argument. And the second cookie will not be set.

ServiceStack Twitter Auth and Registration

I've got an app that has been running using CredentialsAuthProvider() for a while.
Today, I'd like to add twitter and facebook as options for people to create an account. I've got the scaffolding in place.
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(),
new TwitterAuthProvider(appSettings),
new FacebookAuthProvider(appSettings)
}));
//Provide service for new users to register so they can login with supplied credentials.
Plugins.Add(new CustomRegistrationFeature());
The endpoints /api/auth/twitter and /api/auth/facebook work, and I see that a user session is created by visiting /api/auth once the OAuth process is complete HOWEVER...
No user is created in my UserAuthRepository (OrmLiteAuthRepository). The User Id stored in the session is invalid. Any method decorated with the [Authenticate] attribute causes a 404 (User not found) error to be returned.
I would expect that a user is created with the First/Last/Email obtained from Facebook or Twitter. Am I misunderstanding something?
ServiceStack v4.0.38
A few things to check would be the oauth.CallbackUrl has been set correctly taking into account the multiple providers you have registered. Eg http://localhost/auth/{0}.
Also you should check if your IDbConnectionFactory used with your OrmLiteAuthRepository has also been registered with the IoC container. Eg
var dbFactory = new OrmLiteConnectionFactory(
"~/App_Data/db.sqlite".MapHostAbsolutePath(),
SqliteDialect.Provider);
container.Register<IDbConnectionFactory>(dbFactory);
var authRepo = new OrmLiteAuthRepository(dbFactory);
container.Register<IUserAuthRepository>(authRepo);
authRepo.InitSchema();
I wanted to update this thread to say that I've upgraded to ServiceStack 4.0.42 and this issue has resolved itself.

OWIN social facebook provider will not list public profile

I making a facebook login using the new setup in ASPNET mvc 5. When the user login I can see that there is a request for the default public profile, but all I'm getting back is the token, username and nothing else.
I thought that by default I would also get information like first and lastname. And if added to the scope also the email.
On the callback from facebook I'm having this code to extract the information:
var authManager = HttpContext.GetOwinContext().Authentication;
var loginInfo = await authManager.GetExternalLoginInfoAsync();
Thanks to the comment, I was able to find out that I needed to look into the identity of the callback to get the requested information
var loginIdentity = await authManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
This will provide everything beside the logintoken/provider.
I needed to look at the ExternalIdentity and not the ExternalLoginInfo. The post is updated with this info. Also important to add email to the scope

ASP.NET MVC4 WebMatrix WebSecurity Forms authentication: Changing a username

I'm using WebMatrix WebSecurity for my application's Forms Authentication.
The user needs to be able to change his username, without being logged out.
I supposed calling WebSecurity.Logout(), followed by WebSecurity.Login() would do the trick, but Login() requires a password. Of course, I cannot provide this password as it is hashed in the DB.
How can I make this requirement work?
EDIT:
Below are a few suggestions on how to fix the issue of changing the username.
However, my actual problem was that the cookie still holds the old username. I found the following instructions on how to handle that:
http://omaralzabir.com/how_to_change_user_name_in_asp_net_2_0_membership_provider/
So this is how I able to change the user name. This all happens inside a post action method for updating all kinds of user data.
First I check to see if the user name already exists. "info" is a model object coming from the view.
if (WebSecurity.CurrentUserName != info.UserName &&
this.userRepository.Find(info.UserName) != null)
{
ModelState.AddModelError("", "This email is already taken.");
return View();
}
Then I update the database.
UserProfile user = this.userRepository.Find(info.UserId);
user.UserName = info.UserName;
this.userRepository.SaveUser(user);
Then here is the magic part. You have to reset the authorization cookie.
FormsAuthentication.SetAuthCookie(user.UserName, false);
I hope that helps someone out there.
Since WebMatrix WebSecurity is done via UserId, not Username, just change the data in the table you are storing your userdata in, and then redirect them to a new page. You don't need to log them out and back in, I believe the new username will be picked up immediately.
You can do the following steps
Get the UserDetails by UserId or UserName
Change the UserName
Now update the user data by calling the SimpleMembershipProvider.UpdateUser.
More details here

Implementing autologin for ASP.NET MVC

I've been trying to get membership working in ASP.NET MVC. There are some pages that require authentication. Others that can have guests and authorised members. Much like StackOverflow. They can do things anonymously or as a registered user.
We also have a custom database schema of handling members (not the default ASP.NET Membership way). So I'd be looking at writing my own Membership/Principal code. So far its working well, BUT I'm having trouble with sessions expiring and being able to implement the 'Remember me' functionality.
I use FormsAuthentication.SetAuthCookie(username, rememberMe) to set the forms cookie, but say the user leaves the machine for 20mins or IIS recycles my session dies and I get a very inconsistent user state.
Where do I catch the 'Remember me' cookie and how do I handle the user logging in again? Essentially do I store the username and password and then look for the cookie in Application_BeginRequest or something?
If I read you right, it seems like you are expecting ASP.NET to detect the persistent auth cookie and re-establish that user's last session state. It doesn't work that way: the authentication cookie and asp.net session are two independent things, so indeed you will have instances where a user goes back to the site and passes authentication via a persistent AuthCookie, but has brand new (empty) session data.
Assuming the AuthCookie is being set properly, you can detect that the user is authenticated with User.Identity.IsAuthenticated which is not affected by IIS recycles or session expiring. The username is exposed in User.Identity.Name.
If you need to re-initialize some Session data when a user returns to the site you'll have to do that manually. If this is really what you're asking, then it's hard to answer without knowing more about your app, but consider the Session_Start and Session_End events in the global.asax. Alternatively you can just null-check a session object and re-populate whenever it's empty (after session does expire), eg:
//get some user info from session or db if session is null
string preferredName;
if (User.Identity.IsAuthenticated)
{
object o = Session["preferredName"];
if (o == null)
{
preferredName = repository.GetUser(User.Identity.Name).PreferredName;
Session["preferredName"] = preferredName; //save to session so this isn't necessary next time
}
else
{
preferredName = (string)o;
}
}
else
{
preferredName = "anon";
}

Resources