NoWayJose (NoWayJose v1.0.2)
View SourceJWT signing and verification with unified key handling.
Core API
NoWayJose provides a simple, unified API for JWT operations:
Importing Keys
# From PEM (requires algorithm)
{:ok, key} = NoWayJose.import(pem_data, :pem, alg: :rs256, kid: "key-1")
# From DER (requires algorithm)
{:ok, key} = NoWayJose.import(der_data, :der, alg: :es256)
# From JWK (algorithm inferred, verification-only)
{:ok, key} = NoWayJose.import(jwk_json, :jwk)
# From JWKS (returns list of keys)
{:ok, keys} = NoWayJose.import(jwks_json, :jwks)Generating Keys
# RSA keys
{:ok, key} = NoWayJose.generate(:rs256)
{:ok, key} = NoWayJose.generate(:rs256, bits: 4096, kid: "my-key")
# EC keys
{:ok, key} = NoWayJose.generate(:es256, kid: "ec-key")Exporting Keys
# Export as JWK JSON (public key only)
{:ok, jwk_json} = NoWayJose.export(key, :jwk)Signing
{:ok, token} = NoWayJose.sign(key, %{"sub" => "user123"})Verification
{:ok, claims} = NoWayJose.verify(key, token, aud: "my-app")JWKS Fetchers
For external identity providers, use fetchers to automatically refresh keys:
# Start a fetcher
:ok = NoWayJose.start_jwks_fetcher("auth0",
"https://example.auth0.com/.well-known/jwks.json"
)
# Verify using stored keys
{:ok, claims} = NoWayJose.verify_with_stored(token, "auth0", aud: "my-app")JWKS Export
Export stored keys as JWKS JSON for .well-known/jwks.json:
jwks_json = NoWayJose.export_jwks("my-app")
Summary
Functions
Decodes a JWT header without verifying the signature.
Same as decode_header/1, but raises on error.
Removes all keys for a namespace.
Exports a key to the specified format.
Exports stored keys as JWKS JSON.
Generates a new key pair.
Retrieves a key from the key store.
Retrieves all keys for a namespace.
Imports a key from PEM, DER, or JWK format.
Stores a key in the key store.
Signs claims with a key.
Same as sign/3, but raises on error.
Starts a JWKS fetcher for an external endpoint.
Stops a JWKS fetcher.
Verifies a token with a key.
Same as verify/3, but raises on error.
Verifies a token using stored keys.
Same as verify_with_stored/3, but raises on error.
Types
@type alg() ::
:rs256 | :rs384 | :rs512 | :es256 | :es384 | :ps256 | :ps384 | :ps512 | :eddsa
Supported algorithms
A map containing the claims to be encoded
@type token() :: String.t()
JSON Web Token
@type validation_options() :: [validation_option()]
Functions
@spec decode_header(token()) :: {:ok, NoWayJose.Header.t()} | {:error, atom()}
Decodes a JWT header without verifying the signature.
Useful for extracting the kid to look up the correct key.
Examples
{:ok, header} = NoWayJose.decode_header(token)
# => %NoWayJose.Header{alg: "RS256", typ: "JWT", kid: "key-1"}
@spec decode_header!(token()) :: NoWayJose.Header.t() | no_return()
Same as decode_header/1, but raises on error.
@spec delete_keys(String.t()) :: :ok
Removes all keys for a namespace.
Examples
:ok = NoWayJose.delete_keys("my-app")
@spec export(NoWayJose.Key.t(), :jwk | :pem | :der) :: {:ok, String.t() | binary()} | {:error, atom()}
Exports a key to the specified format.
Formats
:jwk- Export as JWK JSON (public key only):pem- Export as PEM string (public key only):der- Export as DER binary (public key only)
Note: JWK-imported keys can only be exported as JWK.
Examples
{:ok, jwk_json} = NoWayJose.export(key, :jwk)
{:ok, pem_string} = NoWayJose.export(key, :pem)
{:ok, der_binary} = NoWayJose.export(key, :der)
Exports stored keys as JWKS JSON.
Only public key components are exported - private key material is never included.
Examples
# Export keys for a namespace
jwks_json = NoWayJose.export_jwks("my-app")
# => ~s({"keys":[{"kty":"RSA","kid":"key-1","n":"...","e":"AQAB"}]})
# Serve at .well-known/jwks.json
get "/.well-known/jwks.json" do
send_resp(conn, 200, NoWayJose.export_jwks("my-app"))
end
@spec generate( alg(), keyword() ) :: {:ok, NoWayJose.Key.t()} | {:error, atom()}
Generates a new key pair.
Algorithm determines key type:
- RSA: :rs256, :rs384, :rs512, :ps256, :ps384, :ps512
- EC: :es256 (P-256), :es384 (P-384)
Options
:bits- RSA key size (default: 2048, ignored for EC):kid- Key identifier (optional)
Examples
{:ok, key} = NoWayJose.generate(:rs256)
{:ok, key} = NoWayJose.generate(:rs256, bits: 4096, kid: "my-key")
{:ok, key} = NoWayJose.generate(:es256, kid: "ec-key")
@spec get_key(String.t(), String.t() | nil) :: {:ok, NoWayJose.Key.t()} | :error
Retrieves a key from the key store.
Examples
{:ok, key} = NoWayJose.get_key("my-app", "key-1")
@spec get_keys(String.t()) :: [NoWayJose.Key.t()]
Retrieves all keys for a namespace.
Examples
keys = NoWayJose.get_keys("my-app")
@spec import(binary(), :pem | :der | :jwk | :jwks, keyword()) :: {:ok, NoWayJose.Key.t()} | {:ok, [NoWayJose.Key.t()]} | {:error, atom()}
Imports a key from PEM, DER, or JWK format.
Formats
:pem- PEM-encoded key (requiresalgoption):der- DER-encoded key (requiresalgoption):jwk- JWK JSON (alg inferred from JWK):jwks- JWKS JSON (returns list of keys)
Options
:alg- Algorithm (required for PEM/DER): :rs256, :rs384, :rs512, :es256, :es384, etc.:kid- Key identifier (optional)
Examples
{:ok, key} = NoWayJose.import(pem, :pem, alg: :rs256, kid: "key-1")
{:ok, key} = NoWayJose.import(jwk_json, :jwk)
{:ok, keys} = NoWayJose.import(jwks_json, :jwks)Notes
JWK-imported keys are verification-only (jsonwebtoken limitation).
@spec put_key(String.t(), NoWayJose.Key.t()) :: :ok
Stores a key in the key store.
Examples
:ok = NoWayJose.put_key("my-app", key)
@spec sign(NoWayJose.Key.t(), claims(), keyword()) :: {:ok, token()} | {:error, atom()}
Signs claims with a key.
Options
:kid- Override the key ID in the JWT header (optional)
Examples
claims = %{"sub" => "user123", "aud" => "my-app"}
{:ok, token} = NoWayJose.sign(key, claims)
# With custom kid
{:ok, token} = NoWayJose.sign(key, claims, kid: "override-kid")
@spec sign!(NoWayJose.Key.t(), claims(), keyword()) :: token() | no_return()
Same as sign/3, but raises on error.
Starts a JWKS fetcher for an external endpoint.
Options
:refresh_interval- Refresh period in ms (default: 15 minutes):retry_interval- Retry on failure in ms (default: 30 seconds):sync_init- Block until first fetch completes (default: false):http_client- Custom HTTP client module:http_opts- Options passed to the HTTP client
Examples
# Async start (returns immediately)
:ok = NoWayJose.start_jwks_fetcher("auth0",
"https://example.auth0.com/.well-known/jwks.json"
)
# Sync start (blocks until keys are loaded)
:ok = NoWayJose.start_jwks_fetcher("google",
"https://www.googleapis.com/oauth2/v3/certs",
sync_init: true
)
@spec stop_jwks_fetcher(String.t()) :: :ok | {:error, :not_found}
Stops a JWKS fetcher.
Examples
:ok = NoWayJose.stop_jwks_fetcher("auth0")
@spec verify(NoWayJose.Key.t(), token(), validation_options()) :: {:ok, claims()} | {:error, atom()}
Verifies a token with a key.
Options
:validate_exp- Validate expiration claim (default:true):validate_nbf- Validate not-before claim (default:true):leeway- Clock skew tolerance in seconds (default:0):iss- Required issuer(s) - string or list of strings:aud- Required audience(s) - string or list of strings:sub- Required subject:required_claims- List of claim names that must be present
Examples
{:ok, claims} = NoWayJose.verify(key, token)
# With validation options
{:ok, claims} = NoWayJose.verify(key, token,
aud: "my-app",
iss: "https://auth.example.com",
leeway: 60
)Errors
Returns {:error, reason} where reason is one of:
:invalid_token- Malformed JWT:invalid_signature- Signature verification failed:expired_signature- Token has expired:immature_signature- Token not yet valid (nbf):invalid_issuer- Issuer doesn't match:invalid_audience- Audience doesn't match:invalid_subject- Subject doesn't match:missing_required_claim- Required claim not present
@spec verify!(NoWayJose.Key.t(), token(), validation_options()) :: claims() | no_return()
Same as verify/3, but raises on error.
@spec verify_with_stored(token(), String.t(), validation_options()) :: {:ok, claims()} | {:error, atom()}
Verifies a token using stored keys.
Automatically extracts the kid from the token header and looks up
the matching key from the key store.
Examples
{:ok, claims} = NoWayJose.verify_with_stored(token, "auth0", aud: "my-app")
@spec verify_with_stored!(token(), String.t(), validation_options()) :: claims() | no_return()
Same as verify_with_stored/3, but raises on error.