mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-19 11:09:42 +00:00
Add JWT Bearer Authentication support and tutorial
This commit is contained in:
1 parent
af7eaaa9b1
commit
cdd957572e
21 files changed
+888
-81
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
../../../mongoose.c
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../mongoose.h
|
||||
Reference in new issue
Block a user