Id token does not contain email when email scope is requested - c#

I have an identity server 4 application and I have added to the email scope [IdentityResources] table in the database. I have also added the email scope to the client that i am using with my client application.
The client application is now prompting the user for email scope consent after login.
I can also see that its there in the UserClaimsPrincipalFactory
protected override async Task
GenerateClaimsAsync(ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
if (user.IsXenaSupporter)
identity.AddClaim(new Claim("Supporter", user.Id.ToString()));
return identity;
}
Identity does contain email. Yet when the Id token and access token are returned to the application neither contain an email. Nor is there an email when i reqeust it from the user info end point.
What do I need to do to populate email address in the claims when the application requests the email scope? Also my custom supporter claim is also not being added

The simple fact that the client application is prompting you for the email scope only means, that the scope was allowed in IdentityServer and requested on the client end but not necessarily that this information is being retrieved.
The magic is in the GetProfileDataAsync method of your IProfileService implementation.
This profile service is where you retrieve whatever claims you'd like and add them to the ProfileDataRequestContext.
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subjectId = context.Subject.GetSubjectId();
Guid.TryParse(subjectId, out Guid g);
//whatever way or wherever you retrieve the claims from
var claimsForUser = idRepo.GetUserClaimsBySubjectId(g);
context.IssuedClaims = claimsForUser.Select(c =>
new Claim(c.ClaimType, c.ClaimValue)).ToList();
return Task.FromResult(0);
}
As explained here, an id token should pretty much only have a sub claim - that's what the userinfo endpoint is for.

The problem was that i had only added it to the [IdentityResources] table.
This simply defines the different scopes. But it doesn't actually assign any data.
To do that i needed to add it to the [IdentityClaims] table.
As soon as i did this the data started being returned in the claims.

Related

Prompt user for additional information during an Open Id Connect event?

Using asp.net Core, Mvc and OpenIdConnect, is it possible to prompt an authenticated user for additional information during the ODIC authentication process, and then redirect back to the originally-desired page?
To give a concrete example: in our system one person, represented by an email address, can have multiple user ids that they may wish to operate under. Assume my email address is tregan#domain.com, and I have 3 user ids to choose from: treganCat, treganDog, treganMouse. When I hit a Controller action that is decorated with the [Authorize] attribute I first go through OpenIdConnect authentication, and one of the claims returned is an email address.
Using that email address, I want the application to prompt me to select the identity that I want to run under (treganDog, treganCat, or treganMouse).
From there, I want the application to take the user id that I selected, interrogate a database for the roles that go along with the selected user id, and load those roles as claims to my identity.
Finally, I want the application to send me on to my desired page (which is the protected Controller method that I originally attempted to visit).
Is this possible?
I'm using an Owin Startup class; the code below "works" except for the fictional line "var identityGuid = [return value from the prompt];" ("fictional" because it represents what I would like to occur, but in fact a series of redirects would be needed).
My example below uses the OnTicketReceived event, but that selection is arbitrary, I would be willing to do this in any event.
services.AddAuthentication(authenticationOptions =>
{
authenticationOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
authenticationOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(openIdConnectOptions =>
{
openIdConnectOptions.Authority = Configuration["PingOne:Authority"];
openIdConnectOptions.CallbackPath = "/Callback";
openIdConnectOptions.ClientId = Configuration["PingOne:ClientId"];
openIdConnectOptions.ClientSecret = Configuration["PingOne:ClientSecret"];
openIdConnectOptions.ResponseType = "code";
openIdConnectOptions.Events.OnTicketReceived = (ticketReceivedContext) =>
{
var emailClaim =
ticketReceivedContext.Principal.Claims.FirstOrDefault(o =>
o.Type == ClaimTypes.Email);
string emailAddress = emailClaim.Value;
//here is where I would like to prompt the user to select an identity based on the email address
//the selected identity is represented by a guid
var identityGuid = [return value from the prompt];
var roles = new MyRepository(myContext).GetRolesForUserId(identityGuid);
var claims = new List<Claim>();
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
ticketReceivedContext.Principal.AddIdentity(new ClaimsIdentity(claims));
return Task.CompletedTask;
};
});
This is impersonation where there is a real user and you need to identify the impersonated user after login.
You will need to complete the login first, return to the app and configure the principal. Then render a UI and receive the selected choice.
You then need your UI to call the back end and tell it to update claims in the auth cookie. Not sure if you'll get this to work though - the impersonated user may need separate storage - such as a second cookie.
This highlights that it can be useful to separate the token / credential the UI receives from the claims the back end works with.
I use the below design a lot for REST APIs that serve UIs directly - though it may be overkill for your solution:
https://authguidance.com/2017/10/03/api-tokens-claims/
I think what I want to do is simply not possible without either figuring out a way to do it inside PingOne or writing my own IdentityServer and taking care of the extra steps there.
I decided to instead write a custom middleware that fires after the Authentication middleware, as described in this SO question: In asp.net core, why is await context.ChallengeAsync() not working as expected?

Understanding storage of OAuth token

I have implemented ASP.NET Identity authentication and OAuth authorization according to this tutorial: http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
It's currently working but i don't fully understand where the TOKEN and it's timer is stored.
This is the code that generates token:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
I would guess that token is stored in the ASP.NET Identity DB or within the hosted WEB API Application, but i don't fully understand.
the token is only generated once by the provider and it is not stored anywhere. It contains the information the application needs to authenticate the request and nothing more.
Assuming you use the Json Web Tokens or JWT, then the token is nothing more than a Json object with some properties, such as when it expires, the actual duration in seconds etc.
The token last for a configurable duration, so assuming you want to reuse that token for multiple calls then the client application will need to store somewhere in a safe manner. It could be in session for example, you could store the whole token and when you need it simply check if it's still active by looking at the duration. If it's not active anymore, you either refresh the current one you have or simply request another.
You could encapsulate all this nicely with something like this :
private TokenModel GetToken()
{
TokenModel result = null;
if (this._systemState.HasValidToken(this._currentDateTime) )
{
result = this._systemState.RetrieveUserData().TokenData;
}
else
{
try
{
result = this._portalApiWrapperBase.RequestAccessTokenData();
}
catch(Exception ex)
{
this.LastErrorMessage = ex.Message;
}
finally
{
this._systemState.AddTokenData(result);
}
}
return result;
}
In my case all this user data is stored in Session and simply retrieved from there.
I am not showing all the code here, but I basically have a state provider where I store the token once I receive it the first time. The next time I need it, if it's still valid, I return it, or go and request another if it's not. All this is hidden from the app, you just call the GetToken method and it deals with everything else.
Now, the token is supposed to be application level, it's generated based on a ClientID and CLientSecret so you could easily request another one when you need to.
The token isn't stored. The user requesting the token needs to be able to pass the token on every request in order to make an authenticated call. So it's the responsibility of the client to store the token in order to do that. (that might be in-memory for short lived sessions or on disk/in a database for longer lived sessions.
There is no need for the server to store the token, since it is passed by the client on each request. One might store it in a db themselves on the server and check if the token is there. Using that kind of mechanism allows you to revoke a token by removing it from the db. There are other ways to do that though.
By timer I guess you mean the lifetime of the token. That is checked by the framework on every request. So there is no actual timer.

How can I get a User object returned when authenticating with ASP.NET Identity 2?

This authentication normally just involves calling the '/Token' endpoint, with user credentials, and receiving a ticket back containing an auth token for the user. I am calling this in my Web API from a WPF client application, and it would make life much easier for me, and the login process much quicker, if I could simple have one authentication request that returns the authenticated IdentityUser, or in a normal template based API project, an AspNetUser object.
I see the method TokenEndPoint in my API's ApplicationOAuthProvider does very little, so I don't think a change there could help much, but the GrantResourceOwnerCredentials seems to be the crux of that provider, yet its return is void.
The options for the provider include:
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin")
but I can find no evidence of that action being executed. I trace all successful requests to a log, and my Debug.WriteLine($"API TRACE: ExternalLogin called for {provider}.") doesn't appear in the output window, so I don't think I can use that action to server redirect to one that returns a User.
Is there anything I can do, except call the /Token endpoint from a login action that allows anonymous, and then redirect?
EDIT: The method that grants the token is in the ApplicationOAuthProvider class provided in my project template. It derives from OAuthAuthorizationServerProvider. It is:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<UserManager>();
var user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", $"The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
It is possible to do a Server.Transfer within the GrantResourceOwnerCredentials method of the ApplicationOAuthProvider : OAuthAuthorizationServerProvider class. This is provided out-the-box in the either the project template or a Nuget package.
The transfer would be to an added Login method in the AccountController, with username, password, and an additional ticket parameter, which will return an AspNetUser user class, to which you can add an extra property for the authentication ticket obtained in GrantResourceOwnerCredentials and normally returned by the /Token resource.
But this is too much meddling in code not written by me, so for this urgent prototype of the project, I just call /Token, and when I have the username, I call /User to get the now signed in AspNetUser.

how to get claims of another user using ASP.NET Core

I'm still learning identities with asp.net core. I'm doing a claims-based token authorization.
Most examples are about "Current" logged in user. In my case my RPC service is receiving a username & password of some user in the identity DB. I need to
verify that a user with such credentials exist
get all the claims of that user
so to verify if the user exists, I'm using this:
ApplicationUser applicationUser = await _userManager.FindByNameAsync(username);
bool exist = await _userManager.CheckPasswordAsync(applicationUser, password);
if (!exist)
{
// log and return
}
I don't know how to do the 2nd step properly. I guess I could do a simple linq to collect all user's claims, but I'm sure there is a better way using the identity methods.
You need to use the GetClaimsAsync() method. For example:
var claims = await _userManager.GetClaimsAsync(applicationUser);
See MSDN

Mvc OAuth2 how to store data for the current Autentication in a OAuthAuthorizationServerProvider

im sorry for my bad English, im french.
I will try to explain my question the best i can.
i have a OAuthAuthorizationServerProvider wish work fine.
This is to allow other application to connect with my Asp.Net Identity 2.0 Authentication Server.
I wish to store data for the current authentication. If the user is connected twice, they will not necessary have the same stored data. I don't think Session is the right thing for this becose i dont use cookie. I use Bearer, an access_token and a refresh_token.
I can simply store the refresh_token in a table, then refer it on each request but i don't like to store sensible data like that, especially if the framework provide a way to do what i want.
I need to store the data relative to each external authentication, not to the user. Something like Claims but only for the current authentication session.
tanks to point me on the right path.
In your OAuthAuthorizationServerProvider, you will have overridden the GrantResourceOwnerCredentials method. This is where you will have validated the user, and it's the place where you can add additional claims for the user.
Here is an example that validates the user against ASPNet Identity, and adds an additional claim to the identity that is returned.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var mgr = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await mgr.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
var usrIdentity = await mgr.CreateIdentityAsync(user, context.Options.AuthenticationType);
foreach (var c in usrIdentity.Claims)
{
identity.AddClaim(c);
}
//
// Add additional claims to your identity
//
identity.AddClaim(new Claim("your_custom_claim", "your_custom_claim_value"));
context.Validated(identity);
}
That said, in your comments you seem to be using Cookie and Token in the same sentence, and possibly confusing the two. Check out this blog post which should give you a good example.
Also check out the ASP.NET Identity Recommended Resources page too.

Categories