Add JWT Bearer Authentication support and tutorial

This commit is contained in:
Sergio R. Caprile committed 2026-07-21 14:25:03 -03:00
1 parent af7eaaa9b1
commit cdd957572e
21 files changed
+888 -81

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
PROG ?= example # Program we are building
DELETE = rm -rf # Command to remove files
OUT ?= -o $(PROG) # Compiler argument for output file
SOURCES = main.c mongoose.c # Source code files
CFLAGS = -W -Wall -Wextra -g -I. # Build options
# Mongoose build options. See https://mongoose.ws/documentation/#build-options
CFLAGS_MONGOOSE += -DMG_ENABLE_LINES=1
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
ifeq ($(OS),Windows_NT) # Windows settings. Assume MinGW compiler. To use VC: make CC=cl CFLAGS=/MD OUT=/Feprog.exe
PROG ?= example.exe # Use .exe suffix for the binary
CC = gcc # Use MinGW gcc compiler
CFLAGS += -lws2_32 -lbcrypt # Link against Winsock library
DELETE = cmd /C del /Q /F /S # Command prompt command to delete files
OUT ?= -o $(PROG) # Build output
endif
all: $(PROG) # Default target. Build and run program
$(RUN) ./$(PROG) $(ARGS)
$(PROG): $(SOURCES) # Build program from sources
$(CC) $(SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(OUT)
clean: # Cleanup. Delete built program and all build artifacts
$(DELETE) $(PROG) *.o *.obj *.exe *.dSYM
+62
View File
@@ -0,0 +1,62 @@
# JWT Bearer Authentication
## Build and run:
```sh
make
```
## HS256:
This path signs and verifies JWTs with the shared secret from `main.c`.
```sh
curl -i http://127.0.0.1:8000/login-hs256
TOKEN=$(curl -s http://127.0.0.1:8000/login-hs256 | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 47
{"sub":"admin","scope":"dash:read dash:write"}
```
`scope` here is a JSON string value containing a space-delimited permission list; as an example.
## ES256 (Requires MG_TLS_BUILTIN):
This path parses the EC private key from `main.c`, signs the JWT with it, and
derives the public key for verification to keep the example self-contained.
```sh
curl -i http://127.0.0.1:8000/login-es256
TOKEN=$(curl -s http://127.0.0.1:8000/login-es256 | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 36
{"sub":"admin","scope":"dash:read"}
```
`scope` here is a JSON string value containing a space-delimited permission list; as an example.
## Unauthorized access attempt example
```sh
curl -i -H "Authorization: Bearer bad.token.here" http://127.0.0.1:8000/
```
```txt
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Length: 13
Unauthorized
```
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// HTTP server example. It issues and verifies JWT Bearer tokens.
#include "mongoose.h"
static const char *s_listen_on = "http://0.0.0.0:8000";
static const char *s_secret = "change-me";
enum { TOKEN_LEN = 1024, CLAIMS_LEN = 256, JWT_LEN = 1024 };
static const char *s_ec_private_key =
"-----BEGIN EC PRIVATE KEY-----\n"
"MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n"
"AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n"
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
"-----END EC PRIVATE KEY-----\n";
#if MG_TLS == MG_TLS_BUILTIN
static size_t verify_es256(struct mg_str token, char *claims, size_t len) {
uint8_t key[32], pub[64];
struct mg_jwt_opts opts = {.public_key = pub};
// This keeps the example self-contained. A real verifier would usually keep
// only the issuer public key, selected by kid, not the signing private key.
if (mg_uecc_parse_private_key(mg_str(s_ec_private_key), key, sizeof(key)) !=
32 ||
mg_uecc_compute_public_key(key, pub, mg_uecc_secp256r1()) != 1) {
return 0;
}
return mg_jwt_verify_es256(token, &opts, claims, len);
}
#endif
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
char user[1], *token, *claims, *jwt;
token = (char *) mg_calloc(1, TOKEN_LEN);
claims = (char *) mg_calloc(1, CLAIMS_LEN);
jwt = (char *) mg_calloc(1, JWT_LEN);
if (token == NULL || claims == NULL || jwt == NULL) {
mg_http_reply(c, 500, "", "OOM\n");
mg_free(token);
mg_free(claims);
mg_free(jwt);
return;
}
mg_http_creds(hm, user, sizeof(user), token, TOKEN_LEN);
if (mg_match(hm->uri, mg_str("/login-hs256"), NULL)) {
struct mg_jwt_opts opts = {.kid = mg_str("hmac-key-1"),
.secret = mg_str(s_secret)};
size_t n;
mg_snprintf(claims, CLAIMS_LEN, "{%m:%m,%m:%m}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("scope"),
MG_ESC("dash:read dash:write"));
opts.claims = mg_str(claims);
n = mg_jwt_sign_hs256(&opts, jwt, JWT_LEN);
mg_http_reply(c, n == 0 ? 500 : 200, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("token"), MG_ESC(n == 0 ? "" : jwt));
#if MG_TLS == MG_TLS_BUILTIN
} else if (mg_match(hm->uri, mg_str("/login-es256"), NULL)) {
uint8_t key[32];
struct mg_jwt_opts opts = {.kid = mg_str("ec-key-1"),
.private_key = key};
size_t keysz, n;
keysz =
mg_uecc_parse_private_key(mg_str(s_ec_private_key), key, sizeof(key));
// ES256 uses a raw 64-byte r || s signature. mg_jwt_sign_es256() signs it
// deterministically; applications can also hash the signing input and
// call mg_uecc_sign() directly if they need random-nonce ECDSA.
mg_snprintf(claims, CLAIMS_LEN, "{%m:%m,%m:%m}", MG_ESC("sub"),
MG_ESC("admin"), MG_ESC("scope"), MG_ESC("dash:read"));
opts.claims = mg_str(claims);
n = keysz == 32 ? mg_jwt_sign_es256(&opts, jwt, JWT_LEN) : 0;
mg_http_reply(c, n == 0 ? 500 : 200, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("token"), MG_ESC(n == 0 ? "" : jwt));
#endif
} else {
struct mg_jwt_opts opts = {.secret = mg_str(s_secret)};
if (mg_jwt_verify_hs256(mg_str(token), &opts, claims, CLAIMS_LEN) > 0) {
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n",
claims);
#if MG_TLS == MG_TLS_BUILTIN
} else if (verify_es256(mg_str(token), claims, CLAIMS_LEN) > 0) {
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s\n",
claims);
#endif
} else {
mg_http_reply(c, 401, "WWW-Authenticate: Bearer\r\n", "Unauthorized\n");
}
}
mg_free(token);
mg_free(claims);
mg_free(jwt);
}
}
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG);
mg_mgr_init(&mgr); // Initialise event manager
mg_http_listen(&mgr, s_listen_on, fn, NULL); // Create HTTP listener
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr);
return 0;
}
+1
View File
@@ -0,0 +1 @@
../../../mongoose.c
+1
View File
@@ -0,0 +1 @@
../../../mongoose.h