Welcome dmjwk
Please welcome dmjwk into the world. This “demo JWK” (or “dumb JWK” if you like) service provides super simple Identity Provider APIs strictly for demo purposes.
Say you’ve written a service that depends on a public JSON Web Key (JWK) set to authenticate JSON Web Tokens (JWT) submitted as OAuth 2 Bearer Tokens. Your users will normally configure the service to use an internal or well-known provider, such as Auth0, Okta, or AWS. Such providers might be too heavyweight for demo purposes, however.
For my own use, I needed nothing more than a Docker Compose file with
local-only services. I also wanted some control over the contents of the
tokens, since my records the sub field from the JWT in an audit trail, and
something like 1a1077e6-3b87-1282-789c-f70e66dab825 (as in Vault JWTs)
makes for less-than-friendly text to describe in a demo.
I created dmjwk to scratch this itch. It provides a basic Resource Owner Password Credentials Grant OAuth 2 flow to create custom JWTs, a well-known URL for the public JWK set, and a simple API that validates JWTs. None of it is real, it’s all for show, but the show’s the point.
Quick Start
The simplest way to start dmjwk is with its OCI image (there are binaries for 40 platforms, as well). It starts on port 443, since hosts commonly reserve that port, let’s map it to 4433 instead:
docker run -d -p 4433:443 --name dmjwk --volume .:/etc/dmjwk ghcr.io/theory/dmjwk
This command fires up dmjwk with a self-signed TLS certificate for localhost
and creates a root cert bundle, ca.pem, in the current directory. Use it
with your favorite HTTP client to make validated requests.
JWK Set
For example, to fetch the JWK set:
curl -s --cacert ca.pem https://localhost:4433/.well-known/jwks.json
By default dmjwk creates a single JWK in the set that looks something like this (JSON reformatted):
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "Ld98DHMIIanlpdOhYf-8GljNHnxHW_i6Bq0iltw9J98",
"y": "xxyRGhCFIjdQFD-TAs-y6uf18wsPvkq8wH_FsGY1GyU"
}
]
}
Configure services to use this URL,
https://localhost:4433/.well-known/jwks.json, to to validate JWTs created by
dmjwk.
Authorization
To fetch a JWT signed by the first key in the JWK set (just the one in this
example), make an application/x-www-form-urlencoded POST with the required
grant_type, username, and password fields:
form='grant_type=password&username=kamala&password=a2FtYWxh'
curl -s --cacert ca.pem -d "$form" https://localhost:4433/authorization
dmjwk stores no actual usernames and passwords; it’s all for show. Provide any username you like and Base64-encode the username, without trailing equal signs, as the password.
Example successful response:
{
"access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJrYW1hbGEiLCJleHAiOjE3NjY5NDQyNzcsImlhdCI6MTc2Njk0MDY3NywianRpIjoiZ3hhNnNib292aTg5dSJ9.04efdORHDA3GIPMnWErMPy4mXXsBfbnMJlzqZsxGVEc2cRvEWI0Mt_IqHDK4RYK_14BCEu2nTMiEPtgwC2IZ5A",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read"
}
Parsing the the access_token JWT from the response provides this header:
{
"alg": "ES256",
"kid": "",
"typ": "JWT"
}
And this payload:
{
"sub": "kamala",
"exp": 1766944277,
"iat": 1766940677,
"jti": "gxa6sboovi89u"
}
We can further customize its contents by passing any of a few additional parameters. To specify an audience and issuer, for example:
form='grant_type=password&username=kamala&password=a2FtYWxh&iss=spacely+sprockets&aud=cogswell.cogs'
curl -s --cacert ca.pem -d "$form" https://localhost:4433/authorization
Which returns something like:
{
"access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzcGFjZWx5IHNwcm9ja2V0cyIsInN1YiI6ImthbWFsYSIsImF1ZCI6WyJjb2dzd2VsbC5jb2dzIl0sImV4cCI6MTc2NzAzNDIyNCwiaWF0IjoxNzY3MDMwNjI0LCJqdGkiOiIxNXZmaDhzYm41YWFxIn0.IGRdD5HGiWLOXggZhb9zPlLK40WWy8R0-HmSuIhaObD6WEwA2WXIBWg_MqtFFQISKLXrjNDHphXtEJsx6FZBOQ",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read"
}
Now the JWT payload is:
{
"iss": "spacely sprockets",
"sub": "kamala",
"aud": [
"cogswell.cogs"
],
"exp": 1767034206,
"iat": 1767030606,
"jti": "8ri9vfsg5f8mj"
}
This allows customization appropriate for your service, which might determine authorization based on the contents of the various JWT fields.
A request that fails to authenticate the username and password, e.g.:
form='grant_type=password&username=kamala&password=nope'
curl -s --cacert ca.pem -d "$form" https://localhost:4433/authorization
Will return an appropriate response:
{
"error": "invalid_request",
"error_description": "incorrect password"
}
Resource
For simple JWT validation, POST a JWT returned from the
authorization API as a Bearer token to
/resource:
tok=$(curl -s --cacert ca.pem -d "$form" https://localhost:4433/authorization | jq -r .access_token)
curl -s --cacert ca.pem -H "Authorization: Bearer $tok" https://localhost:4433/resource -d 'HELLO WORLD
'
The response simply returns the request body:
HELLO WORLD
A request that fails to authenticate, say with an invalid Bearer token:
curl -s --cacert ca.pem -H "Authorization: Bearer NOT" https://localhost:4433/resource -d 'HELLO WORLD'
Returns an appropriate error response:
{
"error": "invalid_token",
"error_description": "token is malformed: token contains an invalid number of segments"
}
That’s It
dmjwk includes a fair number of configuration options, including external certificates, custom host naming (useful with Docker Compose), and multiple key generation. If you find it useful for your demos (but not for production — DON’T DO THAT) — let me know. And if not, that’s fine, too. This is a bit of my pursuit of a thick desire, made mainly for me, but it pleases me if others find it helpful too.