Search code examples
asp.net-identityasp.net-core-2.0razor-pages

Authorization in ASP.NET Core 2.0 via openid provider


I'm having probles with passing claims from external identity to local one. The application is basically the QuickBooksASPNetCore2 Demo with added openid identity provider. Everything seams to work, except the

[Authorize(Role = "some_role")]

on test Razer page.

This is the code that is doing the login logic, and

_signInManager.GetExternalLoginInfoAsync();

return principal with all the claims from identity provider (including roles under the "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" type).

dump of info var with claims

 public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
    {
        if (remoteError != null)
        {
            ErrorMessage = $"Error from external provider: {remoteError}";
            return RedirectToPage("./Login");
        }
        var info = await _signInManager.GetExternalLoginInfoAsync();
        if (info == null)
        {
            return RedirectToPage("./Login");
        }

        // 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: true, bypassTwoFactor : true);
        if (result.Succeeded)
        {
            _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
            await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
            return LocalRedirect(Url.GetLocalUrl(returnUrl));
        }
        if (result.IsLockedOut)
        {
            return RedirectToPage("./Lockout");
        }
        else
        {
            // If the user does not have an account, then ask the user to create an account.
            ReturnUrl = returnUrl;
            LoginProvider = info.LoginProvider;
            if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
            {
                Input = new InputModel
                {
                    Email = info.Principal.FindFirstValue(ClaimTypes.Email)
                };
            }
            return Page();
        }
    }

But when this method exit, the User.Claims on Razor page doesn't have the role claims. It even gets the new "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" claim (with different ID). Did I miss something here?


Solution

  • So if anyone will have this issue, you need to map claims from external identity to local application identity.

    That means to replace the line

    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: true, bypassTwoFactor : true);
    

    with

    var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
    var claimsPrincipal = await this._signInManager.CreateUserPrincipalAsync(user);
    ((ClaimsIdentity)claimsPrincipal.Identity).AddClaims(info.Principal.Claims.Where(c => c.Type == ClaimTypes.Role));
    
    await HttpContext.SignInAsync("Identity.Application", claimsPrincipal);
    

    All the claims that you need in applications, you need to map manually with AddClaim(s) method from external identity.