Search code examples
identityserver4windows-authenticationasp.net-core-3.1

IdentityServer4 Delegation With Windows Authentication User.Identity.Name is null


I have the following flow Asp.Net Core App (App) that calls Asp.Net Core Web API (API1) that inturn calls another Asp.Net Web API (API2)

I'm using IdentityServer4 with Windows Authentication to Authenticate my users here is my code:

ApiResources definition:

new ApiResource("api1", "api1", new List<string> {  JwtClaimTypes.Name, JwtClaimTypes.Email}),
new ApiResource("api2", "api2", new List<string> {  JwtClaimTypes.Name, JwtClaimTypes.Email})

Clients definition

new Client
{
    ClientId = "app1",
    ClientName = "app1",
    ClientSecrets = { new Secret("app1".Sha256())},
    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.OfflineAccess,
        IdentityServerConstants.StandardScopes.Email,
        "api1"
    },
    RedirectUris = { "https://localhost:44375/signin-oidc" },
    FrontChannelLogoutUri = "https://localhost:44375/signout-oidc",
    PostLogoutRedirectUris = { "https://localhost:44375/signout-callback-oidc" },

    AllowOfflineAccess = true,
    RequireConsent = false,
    AccessTokenLifetime = 5
},
new Client
{
    ClientId = "api1",
    ClientSecrets = { new Secret("api1".Sha256())},
    AllowedGrantTypes = {"delegation" },
    AllowedScopes = {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.Email,
        "api2"}
}

Delegation code in API1

public async Task<string> DelegateAsync(string userToken)
{
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync("https://localhost:44382/");
    if (disco.IsError) throw new Exception(disco.Error);

    var tokenResponse = await client.RequestTokenAsync(new TokenRequest()
    {
        Address = disco.TokenEndpoint,
        GrantType = "delegation",
        ClientId = "api1",
        ClientSecret = "api1",
        Parameters =
        {
            {"scope" , "api2 email profile openid" },
            {"token", userToken }
        }
    });

    if (tokenResponse.IsError)
    {
        throw new Exception(tokenResponse.Error);
    }


    _logger.LogInformation($"new: {tokenResponse.AccessToken}");

    return tokenResponse.AccessToken;
}

IdentityServer4 DelegationGrantValidator:

public class DelegationGrantValidator : IExtensionGrantValidator
{
    private readonly ITokenValidator _validator;

    public DelegationGrantValidator(ITokenValidator validator)
    {
        _validator = validator;
    }

    public string GrantType => "delegation";

    public async Task ValidateAsync(ExtensionGrantValidationContext context)
    {
        var userToken = context.Request.Raw.Get("token");

        if (string.IsNullOrEmpty(userToken))
        {
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
            return;
        }

        var result = await _validator.ValidateAccessTokenAsync(userToken);
        if (result.IsError)
        {
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
            return;
        }

        // get user's identity
        var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value;

        context.Result = new GrantValidationResult(sub, GrantType);
        return;
    }
}

in API1 User.Identity.Name = "Domain\UserName" but in API2 User.Identity.Name = null

is there anything missing that I should do solve this issue?

P.S.: if I call the IdentityServer UserInfo endpoint from API2 I'll get the expected UserName


Solution

  • After a lot of search I have finally found the answer.

    I've followed what Rory Braybrook described in this Article and everything is working now