With Implicit Flow on IdentityServer4 I am not receiving User data as Name and Claims in IIdentity when making requests with Authentication Bearer

asp.net-core asp.net-identity-3 c# entity-framework-6 identityserver4

Question

I'm using IdentityServer4 in ASP.NET Core on Framework 4.6.2 with EntityFramework 6 and Asp.Net Identity 2 (not Core).

For this I implemented the IProfileService.
And I am using in my client with Angular 4.x the oidc-client.js library.

I write the access_token in the localStorage to get it and mount the header with the Authentication Bearer.

The problem is that in IPrincipal the Name and Claim are empty. The IsAuthenticated property is false.

My Client configuration:

new Client
{
    ClientName  = "API Client",
    ClientId = IdentityContants.Clients.ImplicitClient.ClientId,
    RequireClientSecret = false,
    AllowedGrantTypes = GrantTypes.Implicit,
    AccessTokenType = AccessTokenType.Jwt,
    AllowAccessTokensViaBrowser = true,
    AlwaysSendClientClaims = true,
    RequireConsent = false,

    RedirectUris = { ... },
    PostLogoutRedirectUris = { ... },
    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityContants.Scopes.ApiScope.Name,
    }
}

The configuration in API:

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
    Authority = IdentityContants.AuthServer,
    ApiName = IdentityContants.Clients.ImplicitClient.ClientId,
    LegacyAudienceValidation = true,
    AllowedScopes = new[]
    {
        "openid",
        "profile",
        IdentityContants.Scopes.ApiScope.Name
    },
    RequireHttpsMetadata = false
});

And I have these settings in Angular 4.x applications:

const settings: any = {
  authority: environment.urls.api.account,
  client_id: environment.clients.api.id,
  redirect_uri: environment.urls.front.apps + '/auth',
  post_logout_redirect_uri: environment.urls.front.apps,
  response_type: 'id_token token',
  scope: 'api.scope openid profile',
  loadUserInfo: true
};

I'm using JwtClaimTypes.Name and ClaimTypes.Name. I tried with and without the JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear().

The ProfileService

public class ProfileService : IProfileService
{
    private readonly AppUserManager _userManager;

    public ProfileService(AppUserManager userManager)
    {
        _userManager = userManager;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var subject = context.Subject;
        if (subject == null) throw new ArgumentNullException(nameof(context.Subject));

        var subjectId = subject.GetSubjectId();

        var user = await _userManager.Users
            .Include( ... the includes )
            .SingleOrDefaultAsync(x => x.Id == subjectId);

        if (user == null)
            throw new ArgumentException("Invalid subject identifier");

        context.IssuedClaims = await GetClaimsFromUserAsync(user);
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var subject = context.Subject;
        if (subject == null) throw new ArgumentNullException(nameof(context.Subject));

        var subjectId = subject.GetSubjectId();
        var user = await _userManager.Context.Users
            .Include( ... the includes )
            .SingleOrDefaultAsync(x => x.Id == subjectId);

        context.IsActive = await ValidateSecurityStamp(user, subject);
    }

    private async Task<bool> ValidateSecurityStamp(Usuario user, ClaimsPrincipal subject)
    {
        if (user == null)
            return false;

        if (!_userManager.SupportsUserSecurityStamp)
            return true;

        var securityStamp = subject.Claims.Where(c => c.Type == "security_stamp").Select(c => c.Value).SingleOrDefault();
        if (securityStamp == null)
            return true;

        var dbSecurityStamp = await _userManager.GetSecurityStampAsync(user.Id);
        return dbSecurityStamp == securityStamp;
    }

    public async Task<List<Claim>> GetClaimsFromUserAsync(Usuario user)
    {
        var claims = new List<Claim>
        {
            new Claim(JwtClaimTypes.Subject, user.Id),
            new Claim(JwtClaimTypes.Name, user.UserName),
            new Claim(JwtClaimTypes.PreferredUserName, user.UserName),
            new Claim(ClaimTypes.Name, user.UserName)  // to test
        };

        if (_userManager.SupportsUserEmail)
        {
            claims.AddRange(new[]
            {
                new Claim(JwtClaimTypes.Email, user.Email),
                new Claim(JwtClaimTypes.EmailVerified, user.EmailConfirmed ? "true" : "false", ClaimValueTypes.Boolean)
            });
        }

        if (_userManager.SupportsUserClaim)
        {
            claims.Add(new Claim(CustomClaimTypes.User.Name, user.Name));
            ... others claims

            claims.AddRange(await _userManager.GetClaimsAsync(user.Id));
        }

        if (_userManager.SupportsUserRole)
        {
            var roles = await _userManager.GetRolesAsync(user.Id);

            claims.AddRange(roles.Select(role => new Claim(JwtClaimTypes.Role, role)));
            claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); // to test
        }

        return claims;
    }
}

And, then Action Login Post:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
    if (ModelState.IsValid)
    {
        var user = await _userManager.Users
            .Include( ... the includes )
            .SingleOrDefaultAsync(x => x.UserName
                .Equals(model.Username, StringComparison.CurrentCultureIgnoreCase));

        if (await _userManager.CheckPasswordAsync(user, model.Password))
        {
            AuthenticationProperties props = null;
            if (AccountOptions.AllowRememberLogin)
            {
                props = new AuthenticationProperties
                {
                    IsPersistent = true,
                    ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
                };
            }

            await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName));
            await HttpContext.Authentication.SignInAsync(user.Id, user.UserName, props);

            if (!_interaction.IsValidReturnUrl(model.ReturnUrl) || !Url.IsLocalUrl(model.ReturnUrl))
                return Redirect(model.ReturnUrl);

            return Redirect(Urls.Apps);
        }

        await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));

        ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
    }

    return View(new LoginInputModel(model.Username, model.ReturnUrl));
}

Nothing work!
What could be wrong?

1
1
5/10/2017 1:02:27 PM

Popular Answer

You need to add the following to your authentication middleware.

TokenValidationParameters = new TokenValidationParameters
{
    NameClaimType = JwtClaimTypes.Name;
}
0
5/10/2017 6:11:48 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow