c# .net core jwt api

Implement JWT Authentication within .Net Core Web API

Matt 2020/04/20 01:23:02
171

In order to protect our data access by valid users from web api endpoint, there're many ways to do for api protection. For that, JWT authentication is one of the popular methods.

What Is JWT ?

JWT, JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

More details can be found from here.

A JWT token contains three parts of it, a Header, a Payload, and a Signature, and they are separated by period ( . dot).

img "JWT Structure"

{
 "alg" : "HS256",
 "typ" : "JWT"
}

Header holds the algorithms such as HMACSHA256, RSA or some many others, more supported algorithms could found here, and the type of token, typecally, it will be "JWT".

- Payload

{
 "loggedInAs": "admin",
 "iat": 1422779638
}

Payload contains claims, additional user data. Please note, DO NOT put sensitive information here for security reasons.

- Signature

HMAC-SHA256(base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + secret)

Signature is calculated by encoding the header and payload using Base64url Encoding and concatenating the two together with a period separator. That string is then run through the cryptographic algorithm specified in the header, in this case HMAC-SHA256. The Base64url Encoding is similar to base64, but uses different non-alphanumeric characters and omits padding.

The three parts are encoded separately by using Base64url Encoding, and concatenated using periods to produce the JWT:

const token = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)

A valid token will look like something shown below,

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

JWT debugger can help us to observe JWT token easily.

img "JWT debugger"

Setting up JWT Bearer

Now, let's move to Visual Studio, we will create a web api project via .net core 3.1.

First, we add some configuration in Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        ......

        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options => {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = Configuration["AppSettings:Jwt:ValidIssuer"],
                    ValidateAudience = true,
                    ValidAudience = Configuration["AppSettings:Jwt:ValidAudience"],
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Configuration["AppSettings:Jwt:SignatureKey"])),
                    ValidateLifetime = true
                };
            });
    }
  • setting up authentication services for JWT bearer

  • the configuration key path should base on your appsettings.config structure

      {
        "AppSettings": {
          "Jwt": {
            "ValidIssuer": "......",
            "ValidAudience": "......",
            "SignatureKey": "......"
          }
        },
        ......
      }
    
  • if need issuer, please remember to enable ValidateIssuer, and set a ValidIssuer

  • if need audience, please remember to enable ValidateAudience, and set a ValidAudience

  • issuer signing key, in many security reasons, and many use cases, we should NOT ignore this part

  • setting up token life time

Then, we add authentication middleware for verifying every http request.

app.UseAuthentication();

Generate Token

Next, let's generate a JWT token, while user login.

    [HttpGet("login/{userName}")]
    public IActionResult Login(string userName)
    {
        if (string.IsNullOrEmpty(userName))
            return BadRequest();

        // step 1. do something for querying user data

        // step 2. build calims
        var defineExpireTime = DateTime.Now.AddDays(3);
        var claims = new ClaimsIdentity(new[] {
            new Claim(JwtRegisteredClaimNames.NameId, dxid),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Nbf, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")),
            new Claim(JwtRegisteredClaimNames.Exp, defineExpireTime.ToString("yyyy-MM-dd HH:mm:ss.ffff")),
            new Claim("Anything", "Custom claim, additional data needed..."),
            new Claim("Anything2", "more detail..."),
            ......
        });

        // step 3. calculate JWT signature, if enabled ValidateIssuerSigningKey in Startup.cs
        var securityKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(this._appSettings?.Jwt?.SignatureKey));

        // step 4-1, generate token by using JwtSecurityTokenHandler
        var tokenHandler = new JwtSecurityTokenHandler();
		
        // the SecurityTokenDescriptor is based on Configuration in Startup.cs
        var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
        {
            Issuer = this._appSettings?.Jwt?.ValidIssuer,
            Audience = this._appSettings?.Jwt?.ValidAudience,
            Subject = claims,
            Expires = defineExpireTime,
            SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
        });

        // step 4-2, serializes a JwtSecurityToken into a JWT in compact serialization format
        var tokenString = tokenHandler.WriteToken(token);

        // final step, add generated token into Http response headers
        HttpContext.Response.Headers.Add("Token", $"{tokenString}");
		
        return Ok();
		
        // or just return tokenString directly
        // return Ok(new { tokenString });
    }

- Registered Claim Names

According to RFC 7519, and .net core documents, there're many "Registered Claim Names" in the IANA "JSON Web Token Claims" registry.

Here are some general used claims.

  • "iss" (Issuer) Claim
  • "aud" (Audience) Claim
  • "exp" (Expiration Time) Claim
  • "nbf" (Not Before) Claim
  • "jti" (JWT ID) Claim
  • "nameId" (NameId) Claim

Authorize

After we generated JWT token, and how to use it while an api (route) is being called ? Just add attribute on targeting actions.

    // people can access api without any authorization
    [AllowAnonymous]
    [HttpGet, Route("any")]
    public IActionResult GetDataByAnonymous()
    {
        // do something
		
        return Ok(new {
            data = "get by anonymous"
        });
    }

    // people who want to access this api MUST pass throuth JWT bearer authorization
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    [HttpGet, Route("jwt")]
    public IActionResult GetDataByJwt()
    {
        // do something
		
        return Ok(new {
            data = "get by jwt"
        });
    }
	

Or, we also can add

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

attribute on a targeting api controller, to make sure the whole actions in that controller all need JWT bearer authorization.

Runs And Tests

After we work hard to create that JWT web api project, let's run it.

- Login

Try to login to grab JWT token inside http requst headers.

img "login to generate JWT token, FFx"

img "login to generate JWT token, Postman"

Yes, we can use jwt.io debugger to verify / observe our token is valid or not.

img "jwt.io verify token"

- Access data with JWT Token

We can access api by the token we grab earlier. Now, let us write some javascript to fetch data.

    fetch('https://localhost:5001/api/tests/jwt', {
        headers: new Headers({
            'Authorization': 'Bearer --a token we grab earlier--',
            'Content-Type': 'application/json; charset=utf-8'
        }),
        method: 'GET'
    }).then(res => res.json())
    .then(json => {
        console.log(json);
        document.getElementById('pre').innerHTML = JSON.stringify(json);
    });

If nothing goes wrong, we would get data we need from api with jwt token authorization.

-in browser
img "jwt 200"

-by postman img "postman 200"

Otherwise, we might get an error with 401 Unauthorized status.

img "jwt 200"

Conclusion

With the powerful .Net Core framework, it can provide us more secure, and easy way for people to access api.

Enjoy it.


References:

JSON Web Token

JWT – Peek into the Jargon “Java Web Token”

How To Use JWT Authentication With Web API

ASP.NET Core 3.1 - JWT Authentication Tutorial with Example API

在 ASP.NET Core WebAPI 中使用 JWT 驗證

JWT.io

Matt