JSON Web Token .NET Core Demo

Lou Mauget .NET Core, C#, Programming, Technology Snapshot Leave a Comment

In this post, I present a tiny .NET Core C# JWT API demo that creates and parses a JSON Web Token (JWT). A self-contained Swagger UI dashboard exercises the API. 

We can’t dead-drop a JWT demo without wrapping it in words about JWT background. I’ll set the scene by introducing tokens, JWTs, and surveying session state residency tradeoffs. We’ll then migrate to, high-level JWT JWT use cases, and arguments about if or when to use JWTs. 

I seek to give equal coverage to JWT upsides and downsides. Let’s get started.

What

  • A token is a special data item that contains enough information to authorize its bearer to claim an action or resource without requiring server-stored session information.
  • A JWT is a standardized RFC 7519 token created by somebody (or something) whom we can verify by a self-contained digital signature.
  • The signature may be symmetrically or asymmetrically cryptographically signed.
  • The JWT is URL-safe via base64-url-encryption.
  • Anybody can read a non-encrypted JWT — the usual use case.
  • JWT can expose standard key names useful for verifying the lifecycle, issuer, and audience.
  • Custom key names are useful for app-specific information.
  • A JWT is useful in SSO or microservice “your friend is my friend” authorizations.

Note that internet literature is rife with controversy about tradeoffs of JWT usage.

State

Applications normally contain a state that carries across requests for responses or actions. State is a record of progress through a task or process. Consider state as a list of keyed variables.

Where do we hold state? HTTP is a stateless protocol, yet most web applications are stateful. Otherwise, a client would need to authenticate at every service request. Let’s look at two approaches to storing state. Please realize that there can be hybrid approaches as well.

Session Cookies

Session cookies are a time-honored approach to managing state on HTTP-based applications. Frameworks normally support this scheme. Not much developer sweat is emitted in their use.

  1. A browser user submits credentials to an application service.
  2. The service matches the credentials to an entry in a user database.
  3. If a match, the server…
    1. creates a unique session key, storing it in the user’s database entry.
    2. associates a cookie containing the opaque session key to the HTTP response.
  4. If no match the user isn’t authenticated … the end.
  5. Otherwise, the browser automatically sends the cookie with requests to the service IP.
    1. The server verifies the session existence, lifespan, and permissions using the session key to the database.
    2. Repeat (5) until session expires or user signs off.

Note that the session store could be server memory or a DBMS.

Session Cookie Tradeoffs

A server creates a browser cookie by sending a Set-Cookie header in an HTTP response. A cookie relies on affinity. It indicates that two requests originated from the same browser. I cannot paste a session cookie value from my FireFox dev console into my Chrome dev console, expecting the session to flow to Chrome.

Pros:

  • Well-supported by web frameworks — easy on the app developer
  • Session ID can be small — it can be any unique key or UUID
  • The session ID carries no embedded content to hack
  • A DBMS-resident session store can survive a service restart
  • Session revocation is easy for the server
  • Good choice for medium-to-small-sized web applications with sticky sessions

Cons:

  • Native mobile apps need cookie support, but cookies are a browser thing
  • A session associates to just one backend — think about horizontal scaling and downstream service flows
  • A SPA may need to deal with CORS if it not loaded from the API host
  • Stateless client-agnostic proper REST APIs now become stateful
  • A memory-resident session store influences the backend dynamic resource footprint
  • A user can disable cookie support.

JWT Approach

Let’s discuss the standardized JWT token approach. Here, authorization data lives and travels in a string.

You can pronounce JWT as “jot”. I say JWT. Let’s examine each of acronym’s three letters right-to-left:

  • T” means “token’. When I say “token” I mean a payload-carrying object that travels bidirectionally between a server and a client. The payload embeds session state.
  • W” means “web” (surprise!). JWT operates with, but is not restricted to the web. A JWT can flow downstream through headless services. No cookies required, although the server can store a JWT in cookie.
  • J” means JSON. Yes, an acronym of acronyms. The JWT consists of JSON objects separated by dots (“.”). JSON is a friendly format. Most application languages or libraries have JSON bindings that effortlessly marshal objects to and from strings that can flow through HTTP headers, URLs, or payloads.

The concept of “stateful token” means session state lives in the token instead of the server. A client holds the state if it holds the token. Session state commonly consists of issuer, audience, authorizations (claims), and session-valid date-times. With JWT, a server could be considered stateless. True RESTFul services are stateless.

A JWT consists of three dot-separated base64-url-encoded pieces:

header.payload.signature
”,

  • Header: declares the encryption scheme of the signature. It could specify “none”, or choices among shared secret schemes or asymmetric schemes using RSA or ECDSA. The server should not totally rely upon the header. Exploits involving “none” have occurred. My demo is coded to use “HS256”, a shared secret 256-bit scheme.
  • Payload: any valid JSON.
  • Signature: the hash resulting from applying the encryption scheme to the header and payload.
See Also:  AWS Amplify GraphQL Queries with TypeScript and Hooks

Passing a JWT

In which cabin of HTTP does a JWT fly? JWT bindings look for a base64-URL-encoded JWT string in the following places, in order of preference:

  1. HTTP Authorization header using the Bearer schema. This avoids polluting query/response data with state stuff. It avoids CORS issues as well.
  2. Cookie. Isolates the state from the data, but has the limitations of cookies.
  3. Data payload. Operable, but mixes state and data.

JWT Security

The JWT is base64-URL-encoded. That’s not to hide the contents, but to tunnel through paths that could try to derive control information from opaque data. Please consider a JWT to be cleartext. Anybody can decode a JWT payload via any online base64 decoder.

We normally secure a JWT by signing its payload. We don’t hide it. Any hiding would be external, often via a connection encrypted as HTTPS / TLS. The JWT signature section is used to verify the identity of the creator of the JWT. Modifying the payload or header part of the JWT invalidates the signature.

Thus, the payload is open! Do not insert sensitive information, such as a password (or your party affiliation). The payload normally contains authorization / permission / role information. I would give a JWT access token a reasonably short-expiry time.

A sender could refresh its access via a refresh JWT. Read the references for the rationale of short-expiry access tokens used with longer-expiry refresh tokens. For this discussion, they’re all JWTs.

C# JWT Encode / Decode Demo

I created a tiny JWT encode and decode demo service. I wanted a no-code UI to accept a payload to a JWT creation API. Another UI-exposed API would accept a JWT, so as to reconstitute the payload and verify the JWT signature: a round-trip API demo.

JWT is intended to interoperate with the World. I found a candy store of choices among languages and frameworks. I wanted my demo to be short and simple. No IDE required would be a plus.

I’d recently worked with a .NET Core API that used a Swagger UI as a development REST dashboard. I used that approach for this demo. The https://jwt.io/#libraries site lists many JWT language bindings. I chose .NET Core “Trivial Libraries”: https://github.com/nuscien/trivial, a kind of Swiss Army Knife for .NET Core. It exposes JWT bindings. The .NET Core framework carries out the build, the runtime, and package management for this demo.

Code

See https://github.com/in-the-keyhole/jwt. There are only four files of source code for the demo. The JwtService is the heart of the demo. It consists of encode, decode, and signature methods that delegate to the Trivial.Security package. It uses a small JwtApi.Model that dictates the payload shape.

using System;
using JwtApi.Models;
using JwtApi.Services.Interfaces;
using Trivial.Security;

namespace JwtApi.Services
{
    /// <summary>
    /// A JSON Web Token API service implementation
    /// </summary>
    public class JwtService : IJwtService
    {
        /// <summary>
        /// Creates a base64-encoded JSON Web Token from a payload model.
        /// </summary>
        /// <param name="payload"></param>
        /// <returns>base64-encoded JWT</returns>
        public string Encode(JwtPayload payload)
        {
            var jwt = new JsonWebToken<JwtPayload>(payload, Signature());
            return jwt.ToEncodedString();
        }

        /// <summary>
        /// Parses a base64-encoded JSON Web Token into a payload model.
        /// </summary>
        /// <param name="token"></param>
        /// <returns>Payload model</returns>
        public JsonWebToken<JwtPayload> Decode(string token)
        {
            return JsonWebToken<JwtPayload>.Parse(token, Signature());
        }

        /// <summary>
        /// Creates a hash signature.
        /// </summary>
        /// <returns>HashSignatureProvider created from non-null environment value of "JWT_SECRET", or literal "secret"</returns>
        private HashSignatureProvider Signature()
        {
            var secret = Environment.GetEnvironmentVariable("JWT_SECRET");
            return HashSignatureProvider.CreateHS256(secret ?? "secret");
        }
    }
}

Installation

I used .NET core 2.2. See https://dotnet.microsoft.com/download. It targets Windows, Mac OS, Linux, or Docker.

It requires no IDE to build and run. High-level instructions:

  1. Navigate to a suitable parent directory
  2. Clone the project from Github
  3. Add the “Trivial” package – it has a JWT encode / decode library
  4. Add Swagger UI for the API demo user interface

In other words, enter these pastable command-window incantations:

github clone https://github.com/in-the-keyhole/jwt 
cd jwt
dotnet add jwtapi  package Trivial
dotnet add jwtapi  package Swashbuckle.AspNetCore

Run

Use the demo to create a JWT from a payload POSTed from the Swagger UI dashboard. The model dictates a hardcoded signature encryption scheme, as well as a default password. Let’s start the demo…

cd JwtApi
dotnet run

Access URL https://localhost:5001/swagger/index.html to view the Swagger UI:

Create a JWT

The API uses a shared secret HS256 scheme for the signature. The default secret value is “secret”. It IS a demo. You could set your own secret via the optional JWT_SECRET environment variable.

Create a JWT by carrying out the following steps in the Swagger UI browser window:

  1. Click POST followed by a click on Try it out.
  2. Enter values in the payload parameter.
  3. Click Execute

The HTTP response code is 200. The response body contains the base64-encoded JWT. It is encoded into base64 so as to pass through the HTTP layer.

Decode a JWT

Copy and paste the POST response body value into the token value of the Swagger GET pane after clicking Try it out there:

Click Execute. Scroll down to the 200 response body to find the payload reconstituted from the token. Notice that Swagger UI gives you a curl command and an HTTP link, as well, for exercising the APIs outside of Swagger UI. If you try the curl command, append -k to the parameters to enable HTTPS / TLS.

See Also:  Running Your Life With Emacs

Note that Swagger also gave you a completed request URL that you can play using Postman or a browserGET.

Official Online JWT Debugger

Let’s independently check a demo-created JWT by pasting it into the official online JWT debugger at https://jwt.io/. It is a bidirectional UI, meaning for whatever data you input on the left or right, reflects on the opposite side.

  1. Set the algorithm to the sharted secret HS256.
  2. Enter the secret value (e.g. “secret”) into the Verify Signature field at Decoded, lower right (must be prior to the following step).
  3. Paste the JWT created by our POST into the Encoded text area.

Verify:

  • The PAYLOAD pane contains the payload that you originally sent to the POST API.
  • Signature Verified appears at the bottom left.
  • Notice that the three sections of the encoded JWT are color-coded.

JWT Trade-Offs

Remember that JWTs are self-contained session state strings sent over the channel. They are normally exposed as base64 cleartext. Granted, they’re not valid to a receiver unless they are unexpired and signed, but the payload IS visible.

By comparison, competing authorization schemes transmit an encrypted key to server-resident state. Often the session key lives in a cookie, leveraging cookie characteristics, pro and con. I discussed session cookie pros and cons earlier. Let’s balance the discussion by enumerating a set of JWT tradeoffs.

JWT Downsides

The debate seems to follow any choice to use JWTs in lieu of using traditional state-keeping approaches. A selection of objections to JWTs includes:

  • Disproportionately huge string sizes result from small payload sizes.
  • Signatures use CPU cycles at every JWT evaluation (but most web frameworks sign session cookies as well).
  • In some uses, a JWT often lives in a signed cookie, causing two levels of signing cycles.
  • JWTs can be overkill in a simple one-to-one client-server web application where easy-to-use session cookies would suffice.
  • Designing a minimal security threat surface requires thought about:
    • tokens passing through hands unknown to the sender;
    • remaining valid until expired;
    • invalidation of JWTs loose in the wild potentially being a “Sorcerer’s Apprentice” situation;
    • complexity – a short-expiry access token plus a long-expiry refresh token enables a way to quickly invalidate all tokens after a detected compromise.
  • No consensus on where to store a JWT, how to revoke a JWT, or how to defend against CSRF / XSS attacks.
  • Browser apps often store JWTs in HTML5 local storage, visible to all apps in that browser, as well as to the human user of the dev console (XSS attacks possible).

JWT Upsides

JWT pluses include:

  • Fit well into friend-of-friend microservice designs: a single authorization server creates a JWT that multiple services recognize and can pass along without sending the user’s credentials.
  • No server-side session table necessary.
  • JWT required in an HTTP header mitigates cross-site-request-forgery (CRSF) exposure of cookies.
  • OAuth2 and OpenID Connect use JWTs to exchange information.
  • JWT works on mobile – no cookies required
  • Operable for users that block cookies
  • Ease in horizontal scaling

Wrap

I didn’t want to dead-drop a JWT demo on you without wrapping it in words about JWT background. I sought to give equal coverage to JWT pros and cons. We touched on:

  • What is a token
  • What is a JSON Web Token, particular
  • Session cookie pros and cons
  • JWT security
  • JWT approach and advantages
  • Upsides and downsides to JWT

I’ve appended a set of references that dance on many sides of the issues of sessions and JWT.

There were choices of languages and frameworks in creating a JWT demo (except, maybe, Forth: https://en.wikipedia.org/wiki/Forth_(programming_language)). I chose .NET Core C# because of a recent favorable experience using .NET Core. It is a satisfying low-impedance pathway for coding, building and running this demo end-to-end. The demo operates on Mac OS, Windows, or Linux. Dependencies are .NET Core, with a couple of NuGet packages added.

The all-but-free dashboard implemented in Swagger UI needed only a couple of lines of configuration to yield in-your-face REST API functionality.

Would I use JWT? Certainly, with OAuth2 or microservices. I’d carefully step around contradictions listed in the literature (e.g. “JWTs are usually stored in local storage” vs “Never ever store a JWT in local storage”). I’d use a refresh token scheme while checking lifecycles, issuers, and audience.

References

JWT Demo Repository
JWT Debugger, Doc, and Library Links
All You Need to Know About User Session Security
Refresh Tokens: When to Use Them and How They Interact with JWTs
My Experience with JWTs
The Hard Parts of JWT Security Nobody Talks About
Security Cheat Sheet – JWT
Cookies
Stop Using JWT for Sessions
Sticky Session
JSON Web Signature
JSON Web Encryption
JSON Web Token
Load Balancing, Affinity, Persistence, Sticky Sessions: What You Need to Know

What Do You Think?