Manage JSON Web Tokens in development with dotnet user-jwts

By Rick Anderson

The dotnet user-jwts command line tool can create and manage app specific local JSON Web Tokens (JWTs).

Synopsis

dotnet user-jwts [<PROJECT>] [command]
dotnet user-jwts [command] -h|--help

Description

Creates and manages project specific local JSON Web Tokens.

Arguments

PROJECT | SOLUTION

The MSBuild project to apply a command on. If a project is not specified, MSBuild searches the current working directory for a file that has a file extension that ends in proj and uses that file.

Commands

Command Description
clear Delete all issued JWTs for a project.
create Issue a new JSON Web Token.
remove Delete a given JWT.
key Display or reset the signing key used to issue JWTs.
list Lists the JWTs issued for the project.
print Display the details of a given JWT.

Create

Usage: dotnet user-jwts create [options]

Option Description
-p | --project The path of the project to operate on. Defaults to the project in the current directory.
--scheme The scheme name to use for the generated token. Defaults to 'Bearer'.
-n | --name The name of the user to create the JWT for. Defaults to the current environment user.
--audience The audiences to create the JWT for. Defaults to the URLs configured in the project's launchSettings.json.
--issuer The issuer of the JWT. Defaults to 'dotnet-user-jwts'.
--scope A scope claim to add to the JWT. Specify once for each scope.
--role A role claim to add to the JWT. Specify once for each role.
--claim Claims to add to the JWT. Specify once for each claim in the format "name=value".
--not-before The UTC date & time the JWT should not be valid before in the format 'yyyy-MM-dd [[HH:mm[[:ss]]]]'. Defaults to the date & time the JWT is created.
--expires-on The UTC date & time the JWT should expire in the format 'yyyy-MM-dd [[[ [HH:mm]]:ss]]'. Defaults to 6 months after the --not-before date. Do not use this option in conjunction with the --valid-for option.
--valid-for The period the JWT should expire after. Specify using a number followed by duration type like 'd' for days, 'h' for hours, 'm' for minutes, and 's' for seconds, for example 365d'. Do not use this option in conjunction with the --expires-on option.
-o | --output The format to use for displaying output from the command. Can be one of 'default', 'token', or 'json'.
-h | --help Show help information

Examples

Run the following commands to create an empty web project and add the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package:

dotnet new web -o MyJWT
cd MyJWT
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Replace the contents of Program.cs with the following code:

using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/", () => "Hello, World!");
app.MapGet("/secret", (ClaimsPrincipal user) => $"Hello {user.Identity?.Name}. My secret")
    .RequireAuthorization();

app.Run();

In the preceding code, a GET request to /secret returns an 401 Unauthorized error. A production app might get the JWT from a Security token service (STS), perhaps in response to logging in via a set of credentials. For the purpose of working with the API during local development, the dotnet user-jwts command line tool can be used to create and manage app-specific local JWTs.

The user-jwts tool is similar in concept to the user-secrets tool, it can be used to manage values for the app that are only valid for the developer on the local machine. In fact, the user-jwts tool utilizes the user-secrets infrastructure to manage the key that the JWTs are signed with, ensuring it’s stored safely in the user profile.

The user-jwts tool hides implementation details, such as where and how the values are stored. The tool can be used without knowing the implementation details. The values are stored in a JSON file in the local machine's user profile folder:

File system path:

%APPDATA%\Microsoft\UserSecrets\<secrets_GUID>\user-jwts.json

Create a JWT

The following command creates a local JWT:

dotnet user-jwts create

The preceding command creates a JWT and updates the project’s appsettings.Development.json file with JSON similar to the following:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Authentication": {
    "Schemes": {
      "Bearer": {
        "ValidAudiences": [
          "http://localhost:8401",
          "https://localhost:44308",
          "http://localhost:5182",
          "https://localhost:7076"
        ],
        "ValidIssuer": "dotnet-user-jwts"
      }
    }
  }
}

Copy the JWT and the ID created in the preceding command. Use a tool like Curl to test /secret:

curl -i -H "Authorization: Bearer {token}" https://localhost:{port}/secret

Where {token} is the previously generated JWT.

Display JWT security information

The following command displays the JWT security information, including expiration, scopes, roles, token header and payload, and the compact token:

dotnet user-jwts print {ID} --show-all

Create a token for a specific user and scope

See Create in this topic for supported create options.

The following command creates a JWT for a user named MyTestUser:

dotnet user-jwts create --name MyTestUser --scope "myapi:secrets"

The preceding command has output similar to the following:

New JWT saved with ID '43e0b748'.
Name: MyTestUser
Scopes: myapi:secrets

Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.{Remaining token deleted}

The preceding token can be used to test the /secret2 endpoint in the following code:

using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();

var app = builder.Build();

app.MapGet("/", () => "Hello, World!");
app.MapGet("/secret", (ClaimsPrincipal user) => $"Hello {user.Identity?.Name}. My secret")
    .RequireAuthorization();
app.MapGet("/secret2", () => "This is a different secret!")
    .RequireAuthorization(p => p.RequireClaim("scope", "myapi:secrets"));

app.Run();