Extra info when using Web Api/OWIN Auth Token - asp.net

I'm using out-of-the-box auth with Individual User Accounts that comes with the Visual Studio template for Web Api. I consume the api in an Angular.js front end.
Not surprisingly, I need to store extra information about the user like first and last names. I would like to set set these extra values in the Register method.
Q. Should I extend the user model or should I store this information as claims?
It would be a bonus if this information was 'automatically' returned in the response from /Token without adding extra code.

I decided to return the extra info in the response to the /Token call.
In ApplicationOAuthProvider (which is part of the template project) I changed CreateProperties and adjusted calls to CreateProperties in 2 places to pass the user, not just the username:
public static AuthenticationProperties CreateProperties(ApplicationUser user)
{
var firstNameClaim = user.Claims.FirstOrDefault(c => c.ClaimType == ClaimTypes.GivenName);
var lastNameClaim = user.Claims.FirstOrDefault(c => c.ClaimType == ClaimTypes.Surname);
var roles = user.Claims.Where(c => c.ClaimType == ClaimTypes.Role).Select(c => c.ClaimValue);
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", user.UserName },
{"firstName", firstNameClaim != null ? firstNameClaim.ClaimValue : "" },
{"lastName", lastNameClaim != null ? lastNameClaim.ClaimValue : "" },
{"roles", string.Join( ",", roles) }
};
return new AuthenticationProperties(data);
}

Related

Claims-based authorization and where to add what the user can do

I just implemented a web api using claims-based authorization. A user can login in the system and a set of claims are pulled from the database and added to the httpContext.User.Identity depending on what the user can do.
After registering the policies in Startup.cs with something like:
services.AddAuthorization(options =>
{
options.AddPolicy(PoliciesDefinitions.RequiresVehicleList, policy => policy.RequireClaim(Permissions.VehiclesList.ToString()));
...
});
I can use the Authorize attribute on the controllers method that I want to authorize with something like:
Authorize(Policy=PoliciesDefinitions.RequiresDriversList)]
[HttpGet]
public ActionResult Get() { ... }
This works ok but today I was reading microsoft documentation a bit more thoroughly and I came across this statement in the Claims-based authorization documentation:
A claim is a name value pair that represents what the subject is, not
what the subject can do
At this time I'm doing exactly what microsfot suggests not to do. I'm adding what the user can do (permissions) to the identity . So, this leads me to think, am I doing it wrong? If the answer is yes, where would you store the user permissions and how would authorization work?
This allows for the KVP, and multiple values.
// with Razor, you did not specific if it was core 2, 3.1 or Razor
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("Vendors", policy =>
policy.RequireClaim("Type.Tykt.org", "Dealer", "Driver", "WholeSaler", "Asset", "Repair"));
});
}
Option 2:
Also there is a claims collection, you can add it after user successfully logs in.
var user = new User {
Email = "xyz#tykt.org",
Name = "xyz"
}
user.Claims.Add(new IdentityUserClaim<string>
{
ClaimType="your-type", // your key
ClaimValue="your-value" // your value
});
await userManager.CreateAsync(user);
Update Ref MSDN:
Its really your choice on how you store retrieve, if I'm understanding the question, your question specifically around is the value of the claim.
Typically, the mapping and verification happens in something like a PermissionHandler : IAuthorizationHandler or a generic approach MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>. Which, loads the values, and handles the requirement verification of a specific permission for e.g. min age, but the actual claims (what are you stating/min age policy vs the value is usually in the DB, like DOB=1/1/1990) travel with the Principal object. Now, where you choose to retrieve the value of the claim is upto you
In the below function he is getting value for the Key from the Context and then validating
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com"))
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(
// He gets the value on the server-side from any store or 3rd party relayer
context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "http://contoso.com").Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}

Identity Server: Access tokens/items set in AuthorizationProeperties in ExternalLoginCallback on the client

Question
I have an identity server implementation that is being used by a number of applications in test and production. I am currently working on a new feature, where the client application using the identity server can perform Azure service management REST api calls. For this, it needs a token. I can generate this token, store it and even access it in the AccountController in the identity server.
My issue is figuring out how to send this to the client. I don't think this token belongs in the claims for the user. So I tried to add it as part of AuthenticationProperties as a token, but I cannot seem to access it in the client. Should I store it in a session like this SO user did link? There is one answer to this question, but that does not seem right (I even tried it out of desperation!)
Relevant sections of code
Generate the token
var resource = "https://management.azure.com/";
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async context =>
{
// Acquire the token for the resource and save it
}
}
}
Restore it in AccountController
public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
{
string resource = "https://management.azure.com/";
// snip
result = await authContext.AcquireTokenSilentAsync(resource, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// snip
AuthenticationProperties props = null;
var tokens = new List<AuthenticationToken>();
var id_token = info.Properties.GetTokenValue("id_token");
if (id_token != null)
{
tokens.Add(new AuthenticationToken { Name = "id_token", Value = id_token });
}
if (result != null)
{
tokens.Add(new AuthenticationToken { Name = "management_token", Value = result.AccessToken });
}
if (tokens.Any())
{
props = new AuthenticationProperties();
props.StoreTokens(tokens);
}
// snip
// Can I access these "props" on the client? I even tried adding it to `Items`, no luck.
await HttpContext.Authentication.SignInAsync(user.UserId, user.DisplayName, provider, props, additionalClaims.ToArray());
}
So, my question, is this the right way go about it? If so, how do I access the authentication properties set? Or should I try saving this in the Session? If so, how do I store it in the client's session?
Any pointers would help. Thank you!
Just wanted to post an answer so that people wanting the same can benefit.
A token cache can be implemented to achieve this. This repository explains how.
Pay special attention to the AdalDistributedTokenCache linked here

User.Identity.Name is always null when using AspNetIdentity with IdentityServer3

I've been trying to setup a new IdentityServer3 with AspNetIdentity for a few days now. I'm able to login using my existing Identity DB and that's all good but I can never get the User.Identity.Name to contain data.
I've tried multiple attempts at adding custom claims & scopes and adding scopes to clients.
Finally, I loaded up the IdentityServer3 Sample repository and tested it out with the webforms client project since it already used the User.Identity.Name in it's About page.
Using WebForms sample client + AspNetIdentity sample server = User.Identity.Name is always null
Using WebForms sample client + SelfHost with Seq sample server = User.Identity.Name with data
I've tried other sample host projects that all populate the User.Identity.Name value just fine.
Now, on the client side I've written a workaround to pull the 'preferred_username' claim value and set the 'name' claim with it.
var id = new claimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
//set the User.Identity.Name value
var name = id.Claims.Where(x => x.Type == "name").Select(x => x.Value).FirstOrDefault() ??
id.Claims.Where(x => x.Type == "preferred_username").Select(x => x.Value).FirstOrDefault();
id.AddClaim(new Claim("name", name));
My questions are:
Why doesn't the AspNetIdentity package fill this by default?
And what do I need to change on the server side so that I don't need to change the client?
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("MyApi", "My Admin API")
{
UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email }
}
};
}
In Identityserver4 you can add the UserClaims to your resource. Fixed it for me.
On IdentityServer4 you can implement IProfileService on server and add the Claim in GetProfileDataAsync
public class AspNetIdentityProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
};
context.IssuedClaims.AddRange(claims);
//Return
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
context.IsActive = (user != null) && ((!user.LockoutEnd.HasValue) || (user.LockoutEnd.Value <= DateTime.Now));
//Return
return Task.FromResult(0);
}
}
Then add "AddProfileService()" to your ConfigureServices method.
services.AddIdentityServer(...)
...
.AddProfileService<AspNetIdentityProfileService>();

AutoMapper: Keep Destination Value if the property doesnt exists in source

I tried to search a lot, and try different options but nothing seems to work.
I am using ASP.net Identity 2.0 and I have UpdateProfileViewModel . When Updating the User info, I want to map the UpdateProfileViewModel to ApplicationUser (i.e. the Identity Model); but I want to keep the values, I got from the db for the user. i.e. the Username & Email address, that doesn't needs to change.
I tried doing :
Mapper.CreateMap<UpdateProfileViewModel, ApplicationUser>()
.ForMember(dest => dest.Email, opt => opt.Ignore());
but I still get the Email as null after mapping:
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
user = Mapper.Map<UpdateProfileViewModel, ApplicationUser>(model);
I also tried this but doesn't works:
public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType.Equals(sourceType) && x.DestinationType.Equals(destinationType));
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
expression.ForMember(property, opt => opt.Ignore());
}
return expression;
}
and then:
Mapper.CreateMap<UpdateProfileViewModel, ApplicationUser>()
.IgnoreAllNonExisting();
All you need is to create a mapping between your source and destination types:
Mapper.CreateMap<UpdateProfileViewModel, ApplicationUser>();
and then perform the mapping:
UpdateProfileViewModel viewModel = ... this comes from your view, probably bound
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
Mapper.Map(viewModel, user);
// at this stage the user domain model will only have the properties present
// in the view model updated. All the other properties will remain unchanged
// You could now go ahead and persist the updated 'user' domain model in your
// datastore

How can I define the clientID (or other data) in a bearer / access token using OWIN

I am trying to figure out how I could put the clientID (or any additional data I might need) inside a bearer/access token.
I am using OWIN OAuth to create the tokens. I can add claims to the identity ticket that will then be ecnrypted/serialized into the token and passed back to the client.
the client then calls a protected API and the API de-serializes the token and sets up an IPrinciple for the user. This identity object contains the username, and the scopes in the ClaimsIdentity.
I would like to get additional information, such as the clientID that made the request to get the token in the first place.
I can put this data inside a claim; this clearly works but its a hack.
I've done quite a bit of searching and I am not sure how, if possible, to store additional data inside the bearer/access token.
Thanks in advance!
You can store it in AuthenticationProperties object as the code below:
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
and to read it you need to unprotect the token as the code below then read the properties from the ticket. Id din't find direct way to create the token without passing the token, I know it is not the ultimate answer but it might help.
string token = "TOKEN GOES HERE";
Microsoft.Owin.Security.AuthenticationTicket ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(token);
If you want to use AuthenticationProperties you must override TokenEndpoint, without that properties will not be returned
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}

Resources