PROWAREtech

articles » current » asp-net-core » add-jwt-authentication-to-minimal-web-api

ASP.NET Core: Json Web Token Authentication Minimal Web API Example

A simple example of adding JWT Bearer authentication and authorization to an ASP.NET Core minimal Web API; with examples written in C#.

This example deals mostly with the server-side implementation. It uses .NET 8/Visual Studio 2022 and its features are new and not included with .NET 7/.NET 6 or earlier versions.

Pay close attention to all the comments in the code particularly ones that begin with "NOTE:".

Create a new minimal Web API project (AOT) in Visual Studio.

Modify Program.cs

Modify the Program.cs file to look like this:


// Program.cs

using Microsoft.AspNetCore.Authentication.BearerToken; // NOTE: THIS CODE NEWLY ADDED
using System.Security.Claims; // NOTE: THIS CODE NEWLY ADDED

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.AddAuthentication().AddBearerToken(); // NOTE: THIS CODE NEWLY ADDED
builder.Services.AddAuthorization(); // NOTE: THIS CODE NEWLY ADDED

builder.Services.ConfigureHttpJsonOptions(options =>
{
	options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();


// NOTE: THE FOLLOWING BLOCK OF CODE NEWLY ADDED
app.MapPost("/signin", async (Signin signin) => // NOTE: THIS IS A POST REQUEST
{
	if (signin.email != "test@test.com" && signin.pwd != "abc123") // NOTE: QUERY A DATABASE OR SOME OTHER DATA STORE FOR THE USER EMAIL AND PASSWORD HERE
		return Results.NotFound();
	var claims = new Claim[] { new Claim(ClaimTypes.Name, signin.email) };
	return Results.SignIn(new ClaimsPrincipal(new ClaimsIdentity(claims, BearerTokenDefaults.AuthenticationScheme)));
});

// NOTE: THE FOLLOWING BLOCK OF CODE NEWLY ADDED
app.MapGet("/user", (ClaimsPrincipal user) =>
{
	return Results.Ok($"You are {user.Identity?.Name}!");
}).RequireAuthorization(); // NOTE: THIS MAKES THE ENDPOINT REQUIRE THAT THE USER BE LOGGED IN



var sampleTodos = new Todo[] {
	new(1, "Walk the dog"),
	new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
	new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
	new(4, "Clean the bathroom"),
	new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
};

var todosApi = app.MapGroup("/todos").RequireAuthorization(); // NOTE: THIS LINE OF CODE MODIFIED; RequireAuthorization() MAKES THESE "todos" ENDPOINTS REQUIRE THAT THE USER BE LOGGED IN
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) => sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo ? Results.Ok(todo) : Results.NotFound());

app.UseDefaultFiles(); // NOTE: THIS CODE NEWLY ADDED
app.UseStaticFiles(); // NOTE: THIS CODE NEWLY ADDED

app.Run();

public record Signin(string email, string pwd); // NOTE: THIS CODE NEWLY ADDED

public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);

[JsonSerializable(typeof(Signin))] // NOTE: THIS CODE NEWLY ADDED
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}

Create jsonman.html

This is used to test the endpoints and adding it is optional.

Create a folder in the project's root named wwwroot.

Paste this HTML code into a new filed named jsonman.html located in the wwwroot folder:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>JSON-MAN</title>
    <style>
        * {
            font-family: sans-serif;
            font-size: 30px;
            color: blue;
        }

        body {
            padding: 3%;
        }

        input, table, td:last-of-type {
            width: 100%;
        }

        textarea {
            width: 100%;
            height: 25vh;
        }
    </style>
    <script type="text/javascript">
/*
request = {
	verb: "GET POST PUT PATCH DELETE",
	path: "/api/",
	headers: {"header1":"value1","header2":"value2"},
	data: "{'is':'json'}",
	onprogress: function(percent){}
};
*/
function ajax2(request) {
	var obj = "object";
	if (typeof request != obj) { request = {}; }
	var undef = "undefined";
	var canPromise = (typeof Promise != undef);
	var xmlobj;
	if (typeof XMLHttpRequest != undef) {
		xmlobj = new XMLHttpRequest();
	}
	else if (typeof window.ActiveXObject != undef) {
		var aVersions = ["MSXML2.XMLHttp.5.0", "MSXML2.XMLHttp.4.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp", "Microsoft.XMLHttp"];
		for (var i = 0; i < aVersions.length; i++) {
			try {
				xmlobj = new ActiveXObject(aVersions[i]);
				break;
			} catch (err) {
				//void
			}
		}
	}
	if (typeof xmlobj != obj) {
		return {then:function(){return{catch:function(ca){ca("XMLHttpRequest object could not be created");}}}};
	}
	if(typeof request.onprogress == "function" && typeof xmlobj.upload == obj) {
		xmlobj.upload.addEventListener("progress", function (event) {
			request.onprogress(Math.floor(event.loaded / event.total * 100));
		});
	}
	// if no verb is specified then use "get"; if no path is specified then use the current file
	xmlobj.open(request.verb || "get", request.path || location.pathname, canPromise);
	xmlobj.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
	if(typeof request.headers == obj) {
		for(var prop in request.headers) {
			xmlobj.setRequestHeader(prop, request.headers[prop]);
		}
	}
	xmlobj.send(request.data || null);
	if(canPromise) {
		return new Promise(function (resolve, reject) {
			xmlobj.onreadystatechange = function () {
				if (xmlobj.readyState == 4) {
					if (xmlobj.status >= 200 && xmlobj.status < 300) {
						resolve(xmlobj.responseText);
					}
					else {
						reject(xmlobj.statusText);
					}
				}
			};
		});
	}
	else {
		if (xmlobj.status >= 200 && xmlobj.status < 300) {
			return {then:function(th){th(xmlobj.responseText);return{catch:function(){}}}};
		}
		else {
			return {then:function(){return{catch:function(ca){ca(xmlobj.statusText);}}}};
		}
	}
}
var headersobj = null;
function setHeadersColor(input) {
	try {
		headersobj = JSON.parse(input.value);
		if (Array.isArray(headersobj)) {
			headersobj = null;
			input.style.color = "red";
		}
		else {
			input.style.color = "#0b0";
		}
	}
	catch {
		headersobj = null;
		input.style.color = "red";
	}
}
function setBodyColor(input) {
	try {
		JSON.parse(input.value);
		input.style.color = "#0b0";
	}
	catch {
		input.style.color = "red";
	}
}
function submitRequestForm(form) {
	ajax2({
		verb: form.requestmethod.value,
		path: form.endpoint.value,
		headers: headersobj,
		data: form.requestbody.value
	}).then(function (txt) {
		form.responsebody.value = txt;
		return false;
	}).catch(function (err) {
		alert(err);
		return false;
	});
	return false;
}
    </script>
</head>
<body>
    <h1>JSON-MAN</h1>
    <form method="get" action="" onsubmit="return submitRequestForm(this);">
        <div>
            <table><tr><td><select name="requestmethod"><option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option></select></td><td><input type="text" name="endpoint" placeholder="ENDPOINT" /></td></tr></table>
        </div>
        <div>
            <input type="text" name="headers" placeholder='HEADERS EXAMPLE: {"header1":"value1","header2":"value2"}' onchange="setHeadersColor(this);" onkeyup="setHeadersColor(this);" autocomplete="off" />
        </div>
        <div>
            <textarea name="requestbody" placeholder="REQUEST BODY" onchange="setBodyColor(this);" onkeyup="setBodyColor(this);"></textarea>
        </div>
        <div>
            <textarea name="responsebody" placeholder="RESPONSE BODY" readonly></textarea>
        </div>
        <div>
            <button type="submit">submit</button>
        </div>
    </form>
</body>
</html>

After these changes, the project's solution explorer should look like this:

Visual Studio Solution Explorer

Run the Application

Hit CTRL+F5 to run the application and navigate to this URL: /jsonman.html

Select to POST to the endpoint /signin and enter a request body (in JSON) with the user's email and password ("test@test.com" and "abc123" in this example).

Copy just the Bearer accessToken to use in futher requests, as done below by adding the header {"Authorization":"Bearer <accessToken>"}. NOTE: this is in JSON format but it is added as a standard HTTP header during the request.

To use a minimal Web API with Blazor, simply add the Authorization header using HttpClient as done in the FetchData.razor page in this example.


PROWAREtech

Hello there! How can I help you today?
Ask any question

PROWAREtech

This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
ACCEPT REJECT