[SOLVED] Identity Server 4 custom token endpoint, get signingcredential at runtime

Issue

I am implementing a custom token endpoint for my identityserver4 project. The goal is to issue a token based on validation of a more complex credentials model (a separate user database than Identity Server’s built in "client/scope" concept) and issue a Jwt token with extra claims added to help with user identity and access rights in my custom api.

My code is something like this:

[HttpPost]
    public IActionResult GetCustomApiToken(CustomUserCredentialsModel credentials)
    {

        var customUser = GetCustomValidatedUser(credentials); //validate user from DB


        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(ApplicationSettings.SigningKey); // <--- DeveloperSigningCredential ???
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[] { new Claim("user", customUser.ToString()) /* extra custom claims */ }),
            Issuer = "my identity server",
            Audience = "my custom api", 
            Expires = DateTime.UtcNow.AddDays(1),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return Ok(tokenHandler.WriteToken(token));

    }

Mind you I have not tested the above completely yet, but something like that should work in Production provided the key is managed in ApplicationSettings.
But it will not work in development where the signing key is added through Identity Server 4’s AddDeveloperSigningCredential() extension.

One solution is to add SigningCredentials in configuration for all Dev/Test environements (= hassle).

Can I resolve the signing credential at runtime (as they are set in Program/Startup) ?

(Also, yes I know: don’t store the signing keys readable in appSettings, please disregard that for the above example.)

Solution

Ok, so I figured it out, you can inject the ISigningCredentialStore singleton and resolve the signingCredential from there:

    private readonly ISigningCredentialStore _signingCredentialStore;
    
    public CustomTokenController(ISigningCredentialStore signingCredentialStore)
    {
       
        _signingCredentialStore = signingCredentialStore ?? throw new ArgumentNullException(nameof(signingCredentialStore));
    }   

    [HttpPost]
    public async Task<IActionResult> GetCustomApiToken(CustomUserCredentialsModel credentials)
    {

        var userId = GetCustomValidatedUser(credentials); 
        if (userId == null) return Unauthorized();

        var signingCredentials = await _signingCredentialStore.GetSigningCredentialsAsync();
        var tokenHandler = new JwtSecurityTokenHandler();
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[] { new Claim("userId", userId.ToString()) /* extra custom claims */ }),
            Issuer = "my IdentityServer",
            IssuedAt = DateTime.UtcNow,
            Audience = "my api",
            Expires = DateTime.UtcNow.AddDays(1),
            SigningCredentials = signingCredentials 
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return Ok(tokenHandler.WriteToken(token));

    }

This worked for me and the Jwt token generated can be validated just like any token issued by the built in "connect/token" endpoint.

Answered By – RoleyBaxter

Answer Checked By – Dawn Plyler (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *