12
votes

There are millions of guides out there, and none of them seem to do what I need. I am creating an Authentication Server, that simply just needs to issue, and validate/reissue tokens. So I can't create a middleware class to "VALIDATE" the cookie or header. I am simply receiving a POST of the string, and I need to validate the token that way, instead of the Authorize middleware that .net core provides.

My Startup Consists of the only Token Issuer Example I could get working.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseExceptionHandler("/Home/Error");

            app.UseStaticFiles();
            var secretKey = "mysupersecret_secretkey!123";
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));

            var options = new TokenProviderOptions
            {

                // The signing key must match!
                Audience = "AllApplications",
                SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),

                Issuer = "Authentication"
            };
            app.UseMiddleware<TokenProviderMiddleware>(Microsoft.Extensions.Options.Options.Create(options));

I can use the middleware on creation since I just need to intercept the body for the username and password. The middleware takes in the options from the previous Startup.cs code, checks the Request Path and will Generate the token from the context seen below.

private async Task GenerateToken(HttpContext context)
{
    CredentialUser usr = new CredentialUser();

    using (var bodyReader = new StreamReader(context.Request.Body))
    {
        string body = await bodyReader.ReadToEndAsync();
        usr = JsonConvert.DeserializeObject<CredentialUser>(body);
    }

    ///get user from Credentials put it in user variable. If null send bad request

    var now = DateTime.UtcNow;

    // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
    // You can add other claims here, if you want:
    var claims = new Claim[]
    {
        new Claim(JwtRegisteredClaimNames.Sub, JsonConvert.SerializeObject(user)),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(JwtRegisteredClaimNames.Iat, now.ToString(), ClaimValueTypes.Integer64)
    };

    // Create the JWT and write it to a string
    var jwt = new JwtSecurityToken(
        issuer: _options.Issuer,
        audience: _options.Audience,
        claims: claims,
        notBefore: now,
        expires: now.Add(_options.Expiration),
        signingCredentials: _options.SigningCredentials);
    var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

    ///fill response with jwt
}

This large block of code above will Deserialize the CredentialUser json and then execute a stored procedure that returns the User Object. I will then add three claims, and ship it back.

I am able to successfully generate a jwt, and using an online tool like jwt.io, I put the secret key, and the tool says it is valid, with an object that I could use

    {
         "sub": " {User_Object_Here} ",
         "jti": "96914b3b-74e2-4a68-a248-989f7d126bb1",
         "iat": "6/28/2017 4:48:15 PM",
         "nbf": 1498668495,
         "exp": 1498668795,
         "iss": "Authentication",
         "aud": "AllApplications"
    }

The problem I'm having is understanding how to manually check the claims against the signature. Since this is a server that issues and validates tokens. Setting up the Authorize middleware is not an option, like most guides have. Below I am attempting to Validate the Token.

[Route("api/[controller]")]
public class ValidateController : Controller
{

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Validate(string token)
    {
        var validationParameters = new TokenProviderOptions()
        {
            Audience = "AllMyApplications",
            SigningCredentials = new 
            SigningCredentials("mysupersecret_secretkey!123", 
            SecurityAlgorithms.HmacSha256),

            Issuer = "Authentication"
        };
        var decodedJwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
        var valid = new JwtSecurityTokenHandler().ValidateToken(token, //The problem is here
        /// I need to be able to pass in the .net TokenValidParameters, even though
        /// I have a unique jwt that is TokenProviderOptions. I also don't know how to get my user object out of my claims
    }
}
1

1 Answers

8
votes

I stole borrowed this code mostly from the ASP.Net Core source code: https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L45

From that code I created this function:

private string Authenticate(string token) {
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    List<Exception> validationFailures = null;
    SecurityToken validatedToken;
    var validator = new JwtSecurityTokenHandler();

    // These need to match the values used to generate the token
    TokenValidationParameters validationParameters = new TokenValidationParameters();
    validationParameters.ValidIssuer = "http://localhost:5000";
    validationParameters.ValidAudience = "http://localhost:5000";
    validationParameters.IssuerSigningKey = key;
    validationParameters.ValidateIssuerSigningKey = true;
    validationParameters.ValidateAudience = true;

    if (validator.CanReadToken(token))
    {
        ClaimsPrincipal principal;
        try
        {
            // This line throws if invalid
            principal = validator.ValidateToken(token, validationParameters, out validatedToken);

            // If we got here then the token is valid
            if (principal.HasClaim(c => c.Type == ClaimTypes.Email))
            {
                return principal.Claims.Where(c => c.Type == ClaimTypes.Email).First().Value;
            }
        }
        catch (Exception e)
        {
            _logger.LogError(null, e);
        }
    }

    return String.Empty;
}

The validationParameters need to match those in your GenerateToken function and then it should validate just fine.