I was going through the identity server 4 doc and I came across this piece of code.
private (TestUser user, string provider, string providerUserId, IEnumerable<Claim> claims) FindUserFromExternalProvider(AuthenticateResult result)
{
var externalUser = result.Principal;
// try to determine the unique id of the external user (issued by the provider)
// the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
throw new Exception("Unknown userid");
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.Remove(userIdClaim);
var provider = result.Properties.Items["scheme"];
var providerUserId = userIdClaim.Value;
// find external user
var user = _users.FindByExternalProvider(provider, providerUserId);
return (user, provider, providerUserId, claims);
}
and it is called like this.
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
I dont quiet understand what is happening here. what sort of function definition usage is this?
You mean the returned data? It's a value tuple that is returned, see this article for a guide to Value Tuples. Value Tuples is a way to return multiple parameters without creating a custom class.
The method tries to lookup the user in the local database after the user has externally authenticated.
Related
I am a stackoverflow noob so please go easy if I am doing this wrong.
I am using asp.net core with the default core identity template (local accounts).
I have accertained how to add claims to user principal when they login locally like so
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var user = await _userManager.FindByNameAsync(model.Email);
await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));
And I have figured out how to get claims returned from the external login but I cannot figure out how I would add these before the user principal gets created in the ExternalLoginCallback function
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
return View(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
else {
// extract claims from external token here
}
// assume add claims to user here before cookie gets created??
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
I am assuming the the _signInManager.ExternalLoginSignInAsync function works similar to the local login _signInManager.PasswordSignInAsync in the sense that once it is called, the cookie will be created. But I am just not sure.
Essentially what I am hoping to achieve, is understanding of how to add custom claims into the cookie that gets created regardless of how to user logins in (local or external), and how to persist these claims to the database if required.
I am planning on doing some work where if I have a user login using say google auth, I need to save that access_token from google, because I wish to call into the Google APIs later with it. So I need to be able to include this access_token in with the User Principal that gets created, and I would hope the cookie would have a claim on it I could use at the front end as well.
This might be out of scope on this question but I would also like when the google token expires, for some-how it to use the refresh token and go get a new one, or force the user to relogin.
Any help on this would be super appreciated, I have really tried hard to understand this without posting this question to stackoverflow. I have read many articles with lots of useful info, but does not provide the answers this specific question is asking. So Thank you very much in advance.
cheers
When you use await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value")); that actually updates the Identity's aspnetuserclaims table.
Whenever you sign in (by using _signInManager.PasswordSignIn or _signInManager.ExternalLoginSignInAsync) the claims from that table are read and added to the cookie that on every request becomes the Principal.
So you probably don't want to be calling the AddClaimAsync method from UserManager on every login.
Regarding external login providers, you have access to the claims when you call (in ExternalCallback and ExternalCallbackConfirmation if you are using the default templates) here:
var info = await _signInManager.GetExternalLoginInfoAsync();
The claims are in info.Principal.Claims.
The access token is not included by default. When it is, it will be here (along with the type and expiry date):
var accessToken = info.AuthenticationTokens.Single(f => f.Name == "access_token").Value;
var tokenType = info.AuthenticationTokens.Single(f => f.Name == "token_type").Value;
var expiryDate = info.AuthenticationTokens.Single(f => f.Name == "expires_at").Value;
To have the access token be included in the AuthenticationTokens collection, when you are configuring the GoogleAuthentication middleware set the SaveTokens flag to true:
app.UseGoogleAuthentication(new GoogleOptions{
ClientId = "...",
ClientSecret = "...",
SaveTokens = true
Now, if you want to have control over which claims go in the cookie you have to "take over" the process of creating the claims principal.
This is done for you when you use _signInManager.PasswordSignIn/ExternalLoginSignInAsync.
So, for example, for ExternalLoginSignInAsync replace:
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
With:
var user = await this._userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var claimsPrincipal = await this._signInManager.CreateUserPrincipalAsync(user);
((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim("accessToken", info.AuthenticationTokens.Single(t => t.Name == "access_token").Value));
await HttpContext.Authentication.SignInAsync("Identity.Application", claimsPrincipal);
"Identity.Application" is the default cookie name. You can change it in Startup's ConfigureServices method, for example to MainCookie:
services.Configure<IdentityOptions>(options => {
options.Cookies.ApplicationCookie.AuthenticationScheme = "MainCookie";
});
You still need to handle the ExternalCallbackConfirmation action in the AccountController. It will be similar to the example above.
I'm using below code to create a ClaimIdentity in OpenIdConnectServerProvider.AuthorizationProvider. But the identity.Name is not searlized. How to allow the OpenIdConnectServer serarlize the name? Thanks.
The previous question is here How to create a ClaimIdentity in asp.net 5
var user = await userManager.FindByNameAsync(context.UserName);
var factory = context.HttpContext.RequestServices.GetRequiredService<IUserClaimsPrincipalFactory<ApplicationUser>>();
var identity = await factory.CreateAsync(user);
context.Validated(new ClaimsPrincipal(identity));
To avoid leaking confidential data, AspNet.Security.OpenIdConnect.Server refuses to serialize the claims that don't explicitly specify a destination.
To serialize the name (or any other claim), you can use the .SetDestinations extension:
var principal = await factory.CreateAsync(user);
var name = principal.FindFirst(ClaimTypes.Name);
if (name != null) {
// Use "id_token" to serialize the claim in the identity token or "access_token"
// to serialize it in the access token. You can also specify both destinations.
name.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
context.Validate(principal);
When adding a claim, you can also use the AddClaim extension taking a destinations parameter:
identity.AddClaim(ClaimTypes.Name, "Pinpoint",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
Background:
I'm building more and more web applications where the designers / template makers decide that adding a "profile picture" and some other user-related data, of course only when someone is logged in.
As most ASP.NET MVC developers I use viewmodels to provide razor layouts with the information that I need shown, sourced from repositories et al.
It is easy to show a user name through using
HttpContext.Current.User.Identity.Name
What if I want to show information that's saved in my backing datastore on these pages? Custom fields in the ApplicationUser class like a business unit name or a profile picture CDN url.
(for sake of simplicity let's assume I use the Identity Framework with a Entity Framework (SQL database) containing my ApplicationUsers)
Question
How do you solve this:
Without poluting the viewmodel/controller tree (e.g. building a BaseViewModel or BaseController populating / providing this information?
Without having to roundtrip the database every page request for these details?
Without querying the database if a user is not logged in?
When you cannot use SESSION data (as my applications are often scaled on multiple Azure instances - read why this isn't possible here- I'm not interested in SQL caching or Redis caching.
I've thought about using partials that new their own viewmodel - but that would still roundtrip the SQL database every pageload. Session data would be safe for now, but when scaled up in azure this isn't a way either. Any idea what would be my best bet?
TLDR;
I want to show user profile information (ApplicationUser) on every page of my application if users are logged in (anon access = allowed). How do I show this info without querying the database every page request? How do I do this without the Session class? How do I do this without building base classes?
The best way with Identity is to use claims to store custom data about the user. Sam's answer pretty close to what I'm saying here. I'll elaborate a bit more.
On ApplicationUser class you have GenerateUserIdentityAsync method which used to create ClaimsIdentity of the user:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
new Claim("MyApp:LastName",this.LastName),
});
return userIdentity;
}
This adds key-value pairs on the user identity that is eventually serialised and encrypted in the authentication cookie - this is important to remember.
After user is logged in, this Identity are available to you through HttpContext.Current.User.Identity - that object is actually ClaimsIdentity with claims taken from the cookie. So whatever you have put into claims on login time are there for you, without having to dip into your database.
To get the data out of claims I usually do extension methods on IPrincipal
public static String GetFirstName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new DomainException("User is not authenticated");
}
var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
if (personNameClaim != null)
{
return personNameClaim.Value;
}
return String.Empty;
}
This way you can access your claims data from your Razor views: User.GetFirstName()
And this operation is really fast because it does not require any object resolutions from your DI container and does not query your database.
The only snag is when the values in the storage actually updated, values in claims in the auth cookie are not refreshed until user signs-out and signs-in. But you can force that yourself via IAuehtenticationManager.Signout() and immediately sign them back in with the updated claims values.
You could store your extra information as claims. In your log in method fill your data to generated identity. For example if you are using Identity's default configuration you could add your claims in ApplicationUser.GenerateUserIdentityAsync() method:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaims(new[]
{
new Claim("MyValueName1","value1"),
new Claim("MyValueName2","value2"),
new Claim("MyValueName2","value3"),
// and so on
});
return userIdentity;
}
And in your entire application you have access those information by reading current user claims. Actually HttpContext.Current.User.Identity.Name uses same approach.
public ActionResult MyAction()
{
// you have access the authenticated user's claims
// simply by casting User.Identity to ClaimsIdentity
var claims = ((ClaimsIdentity)User.Identity).Claims;
// or
var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
}
I think the "how to" is a little subjective as there are probably many possible ways to go about this but I solved this exact problem by using the same pattern as HttpContext. I created a class called ApplicationContext with a static instance property that returns an instance using DI. (You could alter the property to generate a singleton itself as well if you aren't, for some reason, using DI.)
public interface IApplicationContext
{
//Interface
string GetUsername();
}
public class ApplicationContext : IApplicationContext
{
public static IApplicationContext Current
{
get
{
return DependencyResolver.Current.GetService<IApplicationContext>();
}
}
//appropriate functions to get required data
public string GetUsername() {
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
return HttpContext.Current.User.Identity.Name;
}
return null;
}
}
Then you just reference the "Current" property in your view directly.
#ApplicationContext.Current.GetUsername()
This would solve all of you requirements except #2. The database call may not add a significant enough overhead to warrant avoiding altogether but if you require it then your only option would be to implement some form of caching of the user data once it is queried the first time.
Simply implement ChildAction with caching and vary by loggedin user
In Nancy FX how can I use the IUserMapper (if at all) to change a logged in users account details (name, email, password)?
// registering is straight forward
Post["/register", true] = async(parameters, ct) =>
{
var user = this.BindAndValidate<UserRegistration>();
var response = await mapper.RegisterUser(user); // user is registered
...
}
// but how can I change a registered user's details?
Post["/profile", true] = async(parameters, ct) =>
{
this.RequiresAuthenticationAndLogOut();
var user = this.BindAndValidate<UserRegistration>();
var response = await mapper.?????(user);
...
}
You wouldn't use the IUserMapper at all, this really only exists for authentication purposes and nothing more.
When a user is authenticated then you get access to the UserName property. If you setup your mapper to assign the user's Id to the UserName then you can load your user, modify, and commit.
i.e:
Post["/profile", true] = async(parameters, ct) =>
{
this.RequiresAuthenticationAndLogOut();
var user = this.BindAndValidate<UserRegistration>();
var existingUser = await db.LoadAsync(int.Parse(CurrentUser.UserName));
existingUser.Name = user.Name;
...
return ...;
}
Also, you should never persist an object that's been bound from a client. The user may submit additional information you don't want them to.
Also I don't know where you got your IUserMapper from because in Nancy there is no Register.
I'm trying to customize my own implementation of ExtendedMembershipProvider. I have no idea what the GetUserIDFromOauth method is supposed to do? I see it is throwing an exception by default, and that it is supposed to return the user ID from the open auth provider.
I fail to see how this is supposed to be done, unless this means find if that user exists in the system? Is that it's purpose? I find the lack of documentation confusing...
Thanks.
GetUserIdFromOAuth is a method used by ExtendedMembershipProvider class to find User.Id in your table of users in your web application database based on Provider and ProviderUserId that you get from OAuth or OpenId Provider. After getting Provider and ProviderUserId data for a specified user, you need to save it in your database.
It returns throw new NotImplementedException(); by default. You need to implement this method to return an integer of your User.Id from your application database.
This is a sample implementation:
public override int GetUserIdFromOAuth(string provider, string providerUserId)
{
using (var context = new YourApplicationEntities())
{
// Try to find user with certain Provider and ProviderUserId
var user = context.Users.SingleOrDefault(
q => q.Provider == provider &&
q.ProviderUserId == providerUserId
);
if (user != null)
{
return user.Id;
}
}
return -1;
}
This implementation assumed that you have Provider and ProviderUserId field in your User table. If this information saved in a different table, you just need to modify the LINQ to return the desired result.