Save claims to db on login - asp.net-mvc

I have an MVC5 application that is using Oauth and is logging in correctly however I want to save the returned email to the database and am having trouble doing this.
I have the following code in startup.auth.cs:
The below code adds the claim fine and I can the email claim in the debugger, however if it is the users first login then the user isn't actually created at this point so I can't update the user here.
LinkedInAuthenticationOptions x = new LinkedInAuthenticationOptions();
x.ClientId = "key";
x.ClientSecret = "key";
x.Scope.Add("r_emailaddress");
x.Provider = new LinkedInAuthenticationProvider()
{
OnAuthenticated = async context =>
{
//Get the access token
context.Identity.AddClaim(
new System.Security.Claims.Claim("Email", context.Email));
}
};
app.UseLinkedInAuthentication(x);
In the below code (in the default project accountcontroller.cs) I try to access the email claim that I added above however it is unable to find the claim...
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
ClaimsIdentity identity = UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
}
Does anybody know how I can access the email claim that I added in startup.auth.cs ?

You want to do something similar to this blog post, just store the email claim instead of the Facebook access token.
How to get more data from external providers?

Related

MVC sign in to IdentityServer4 without redirect

So I'm trying to sign in users from my ASP.NET Core 2.2 MVC app without redirecting them to IdentityServer4. So far I'm able to use IS4's ResourceOwnerPassword flow to get a token and get a token with RequestPasswordTokenAsync, but even after I set my client with the access token it's not authenticating my app.
Controller:
public async Task<IActionResult> Login()
{
var client = new HttpClient();
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = "http://localhost:5000/connect/token",
ClientId = "mvc3",
ClientSecret = "secret",
UserName = "LegitUsername",
Password = "VeryLegitamitePassword",
Scope = "api1"
});
client.SetBearerToken(response.AccessToken);
return Redirect("/");
}
Current behavior: Token is granted, but my header still has the "Login" button
Expected behavior: Token is granted, "Logout" button is displayed
The current behavior suggests that I haven't been authenticated even though I'm holding the token. I know I'm missing something to pass the token to the HttpContext to authenticate my app, but can't figure out what. Any help would be appreciated.
Well, you do not log in the user. You request an access token from id4. Normally you request an access token to add it to a request (as you did) to access a resource.
Please refer to the examples: https://github.com/IdentityServer/IdentityServer4.Samples/tree/master/Clients/src
There are several examples implementations for mvc.

IdentityServer4: How to include email in access_token without the client explicitly requesting it?

when the user logs in on the IdentityServer4 via Google, I'd like to access the email (and maybe their google-id) but without having the client request it. So it should be accessible every time, so I can put it in the access_token (because our API needs the user's email address).
I've been injecting into the IProfileService, also the IClaimsService but I can't find the email there. Would it be possible to hook into the Google-SignIn Callback so I can access the response manually?
Thanks!
I solved it by adding the claims I needed in AccountController.ExternalLoginCallback like this:
//Add E-Mail claim even if client didn't ask for it
if (claims.Exists(c => c.Type.Equals(ClaimTypes.Email))) {
additionalClaims.Add(new Claim(JwtClaimTypes.Email, claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.Email)).Value));
}
then I added the claim to the access_token by dependency injecting my ProfileService class and adding the claims in the MyProfileService.GetProfileDataAsync like this:
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var claims = new List<Claim>();
Claim emailClaim = context.Subject.Claims.Where<Claim>(claim => claim.Type.Equals(JwtClaimTypes.Email)).FirstOrDefault();
if (emailClaim != null)
{
claims.Add(emailClaim);
}
context.IssuedClaims = claims;
return Task.FromResult(0);
}

OpenID Connect ASP NET MVC AAD

I implemented a sample app using OpenID Connect standard with ASP NET MVC website. My goal was to outsource storing sensitive data to Azure so i used Azure Active Directory. Since it's impossible to add custom properties to users in Azure i store non sensitive user Claims in our private db. I managed to get this claims and "add" them to the cookie like this:
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = context =>
{
var objectId = context.AuthenticationTicket.Identity.Claims.First(x => x.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier");
var claims = GetUserClaims(objectId.Value);
foreach (var item in claims)
{
context.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim(item.Key, item.Value));
}
return Task.FromResult(0);
}
}
This way I added required claims to the cookie so those claims persist in my User object until user sign-out which is fine but there is one Claim which can change during the session ( basically user can change it on one page ). The problem is I can't find how to "change" this Claim in the cookie so it will persist. Ideally I would like to somehow force
AuthorizationCodeReceived
function to be called again. Is it possible ? Or is there another way where I can swap the value stored in the cookie ?
So far my only solution is to log-out user when he change this value so it will force him to sign-out again and my callback for AuthorizationCodeReceived will be called again, but it's not a very user-friendly way.
You can call HttpContext.GetOwinContext().Authentication.SignIn() after you add a claim in identity object to persist the new claim in cookie.

Invalidate Old Session Cookie - ASP.Net Identity

An external company has done some penetration tests on the ASP.NET MVC 5 application i'm working on.
An issue that they raised is described below
A cookie linked with session Management is called AspNet.ApplicationCookie. When entered manually,the application authenticates the user. Even though the user logs out from the Application,the cookie is still valid. This means,the old session cookie can be used for a valid authentication within unlimited timeframe. In the moment the old value is inserted, the application accepts it and replaces it with a newly generated cookie. Therefore, if the attacker gains access to one of the existing cookies, the valid session will be created,with the same access as in the past.
We're using ASP.NEt Identity 2.2
Here's our logout action on the account controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
return RedirectToAction("Login", "Account");
}
in startup.auth.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromHours(24.0),
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, int>(
validateInterval: TimeSpan.FromMinutes(1.0),
regenerateIdentityCallback: (manager, user) =>
user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())))
}
});
I would have thought that the framework would have taken care of invalidating an old session cookie but browsing through the Owin.Security source code it appears not.
How do i invalidate the session cookie on logout?
edit on Jamie Dunstan's Advice i've added AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); but then has made no difference. I can still still log out of the application, clone a previously authenticated request in Fiddler, and have it accepted by the application.
Edit : My updated Logoff method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> LogOff()
{
var user = await UserManager.FindByNameAsync(User.Identity.Name);
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
await UserManager.UpdateSecurityStampAsync(user.Id);
return RedirectToAction("Login", "Account");
}
Make sure you use AuthenticationManager.Signout(DefaultAuthenticationTypes.ApplicationCookie); as correctly suggested by Jamie.
Being able to login with the same cookie again is by design. Identity does not create internal sessions to track all logged-in users and if OWIN gets cookie that hits all the boxes (i.e. copies from the previous session), it'll let you login.
If you still can login after the security stamp is updated, most likely OWIN can't get a hold of ApplicationUserManager. Make sure you have this line just above the app.UseCookieAuthentication
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
Or if you are using DI take ApplicationUserManager from DI:
app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());
Also reduce the validateInterval: TimeSpan.FromMinutes(30) to lower value - I usually settle for couple minutes. This is how often Identity compares values in auth-cookie to the values in the database. And when the comparison is done, Identity regenerates the cookie to update timestamps.
Trailmax's answer is spot on, I thought I would add that if someone is trying to do this while also using ASP.NET Boilerplate, the following is what I used to make this work:
app.CreatePerOwinContext(() => IocManager.Instance.Resolve<UserManager>());
I originally had:
app.CreatePerOwinContext(() => IocManager.Instance.ResolveAsDisposable<UserManager>());
and is wasn't working.
you were on the right way. Indeed the easiest way is to update user SecurityStamp but
normally executing it doesn't lead to success because actualy credentials aren't changed and it remains the same in db.
Solution, try this:
private string NewSecurityStamp()
{
return Guid.NewGuid().ToString();
}
private async Task RegenerateSecurityStamp(string userId)
{
var user = await _userManager.FindByIdAsync(userId);
if (user != null)
{
user.SecurityStamp = NewSecurityStamp();
await _userStore.UpdateAsync(user);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> LogOff()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
await RegenerateSecurityStamp(User.Identity.GetUserId());
return RedirectToAction("Login", "Account");
}

problems with DotNetOpenAuth OAuth2 Client for Google

I have an ASP.NET MVC 4 application.
Yesterday my users started to complain they cannot login using their Google accounts. After lots of googling I found this: DotNetOpenAuth.GoogleOAuth2. I followed the instructions.
I created Client ID for web applications in Google console.
In AuthConfig.RegisterAuth() I have:
var client = new DotNetOpenAuth.GoogleOAuth2.GoogleOAuth2Client(googleClientID, googleClientSecret);
var extraData = new Dictionary<string, object>();
OAuthWebSecurity.RegisterClient(client, "Google", extraData);
In AccountController, I have something like this:
public ActionResult ExternalLoginCallback(string returnUrl)
{
DotNetOpenAuth.GoogleOAuth2.GoogleOAuth2Client.RewriteRequest();
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
{
// here I have some logic where is user sent when login was successfull
return RedirectToLocal(returnUrl);
}
if (User.Identity.IsAuthenticated)
{
// If the current user is logged in add the new account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
return RedirectToLocal(returnUrl);
}
else
{
// User is new, ask for their desired membership name
string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
// some logic
return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = username, ExternalLoginData = loginData, EncryptedEmail = encryptedEmail });
}
}
I have two problems:
Before the change, the result.UserName contained the users email. Now it contains name. But I need email. Except for this, registration works fine.
My biggest problem - existing users cannot log in using their google account. The code goes to "// User is new, ask for their desired membership name" for them. The ProviderUserId I get now is different for the same email address.
Thanks a lot for any advice.
Can you configure this library to pass additional parameters to the Google authorization service? If so, you should pass 'openid.realm=$your_app_openid2_realm' (if your app was configured for OpenID2 before, it most likely asserted a 'realm' value in its requests, you should use the same value).
In this case, you'll receive two identifiers from Google. The new one (which is compatible with profile URLs and overall more Google APIs) and the old one (returned as openid_id).

Resources