jwt
Making and checking JWT tokens, and guarding API routes.
import jwt
A JWT is a token the server signs after a user logs in and the client sends with every request; by checking the signature the server knows who the user is without going to the database. This package supports the HMAC algorithms, HS256, HS384 and HS512, and never accepts a token with the none algorithm.
For an API, make a Guard and call Require or one of its siblings in every handler; when the token is wrong the error response is sent for you. The secret has to be long and random, for instance from RandomToken in crypto.
Types
Token
struct Token| Name | Type | Description |
|---|---|---|
alg | str = "" | The signing algorithm, such as HS256. |
typ | str = "" | The type, usually JWT. |
kid | str = "" | The key id, when the header carries one. |
claims | Claims | The standard claims. |
header_json | str = "" | The JSON text of the header. |
payload_json | str = "" | The JSON text of the body. |
signing_input | str = "" | The part that was signed: the header and the body with a dot between them. |
signature | str = "" | The signature as url safe base64. |
valid | bool = false | The signature and the times were checked and were right. |
import jwt
func main:
mut c := jwt.NewClaims()
c.iss = "salamlang.ir"
c.sub = "42"
c.aud = "api"
c.iat = 1700000000
c.exp = 1700003600
jwt.SetStr(c, "name", "Maryam")
jwt.SetInt(c, "level", 3)
jwt.SetBool(c, "admin", false)
jwt.SetRaw(c, "scope", `"read write"`)
jwt.SetRaw(c, "roles", `["editor","viewer"]`)
println jwt.ClaimsJSON(c)
mut err := 0
token := jwt.SignHS256(c, "a-long-random-secret", err)
println err == jwt.JWT_OK
println token
mut o := jwt.DefaultOptions()
o.now = 1700001000
o.issuer = "salamlang.ir"
o.audience = "api"
t := jwt.Verify(token, "a-long-random-secret", o, err)
println t.valid, t.alg, t.typ, t.claims.sub
println jwt.Str(t, "name"), jwt.Int(t, "level"), jwt.Bool(t, "admin"), jwt.Has(t, "email")
println jwt.Raw(t, "level"), jwt.HasScope(t, "write"), jwt.HasAudience(t, "api"), jwt.ListHas(t, "roles", "editor")
_ := jwt.Verify(token, "another-secret", o, err)
println err == jwt.JWT_ERR_SIGNATURE, jwt.ErrorText(err)
o.now = 1800000000
_ := jwt.Verify(token, "a-long-random-secret", o, err)
println err == jwt.JWT_ERR_EXPIRED, jwt.ErrorText(err)
o.now = 1700001000
o.issuer = "example.com"
println jwt.IsValid(token, "a-long-random-secret", o)
_ := jwt.Decode("abc", err)
println err == jwt.JWT_ERR_MALFORMED
endClaims
struct Claims| Name | Type | Description |
|---|---|---|
iss | str = "" | The issuer. |
sub | str = "" | The subject, usually the user id. |
aud | str = "" | The audience. |
aud_raw | str = "" | The audience as JSON, when it is an array. |
exp | i64 = 0 | The expiry time in Unix seconds. |
nbf | i64 = 0 | The time before which it is not valid. |
iat | i64 = 0 | The issue time. |
jti | str = "" | The unique id of the token. |
extra | str = "" | The other claims, as JSON. |
The example for this item is shown with the ones above: jwt-Sign.salam
Options
struct Options| Name | Type | Description |
|---|---|---|
alg | str = "HS256" | The algorithm expected; a token with another one is rejected. |
issuer | str = "" | The required issuer; empty means do not check it. |
audience | str = "" | The required audience. |
subject | str = "" | The required subject. |
leeway | i64 = 0 | How many seconds of clock difference to accept. |
now | i64 = 0 | The time to check against; zero means now. For testing. |
require_exp | bool = false | Reject a token with no exp. |
require_nbf | bool = false | Reject one with no nbf. |
require_iat | bool = false | Reject one with no iat. |
require_sub | bool = false | Reject one with no sub. |
require_jti | bool = false | Reject one with no jti. |
check_typ | bool = true | Check that the type is JWT. |
import jwt
func main:
println jwt.JWT_HS256, jwt.JWT_HS384, jwt.JWT_HS512
println jwt.IsSupportedAlg("HS512"), jwt.IsSupportedAlg("none"), jwt.DigestSize("HS384")
mut c := jwt.NewClaims()
c.sub = "7"
c.jti = "t-1"
jwt.ExpiresIn(c, 3600)
jwt.NotBeforeIn(c, 0)
mut err := 0
one := jwt.SignHS384(c, "k", err)
two := jwt.SignHS512(c, "k", err)
three := jwt.Sign(c, jwt.JWT_HS256, "k", err)
four := jwt.SignWithKid(c, "HS256", "key-2026", "k", err)
println jwt.Decode(one, err).alg, jwt.Decode(two, err).alg, jwt.Decode(four, err).kid
t := jwt.VerifyHS256(three, "k", err)
println t.valid, jwt.SecondsRemaining(t) > 3500
b := jwt.Verify(one, "k", jwt.OptionsFor("HS384"), err)
println b.valid
_ := jwt.Verify(one, "k", jwt.DefaultOptions(), err)
println jwt.ErrorText(err)
raw := jwt.SignJSON(`{"sub":"1"}`, "HS256", "", "k", err)
println raw
d := jwt.Decode(raw, err)
println d.header_json, d.payload_json
println jwt.SignatureOf(d.signing_input, "HS256", "k") == d.signature
encoded := jwt.B64UrlEncode("hello?")
println encoded.len()
mut ok := false
println jwt.B64UrlDecode(encoded, ok), ok
println jwt.B64UrlEncodeBytes("hi?" as void*, 3)
_ := jwt.SignHS256(c, "", err)
println err == jwt.JWT_ERR_EMPTY_SECRET, jwt.ErrorText(err)
println jwt.JWT_ERR_BAD_BASE64, jwt.JWT_ERR_BAD_JSON, jwt.JWT_ERR_ALG_UNSUPPORTED, jwt.JWT_ERR_ALG_MISMATCH
println jwt.JWT_ERR_NOT_YET_VALID, jwt.JWT_ERR_ISSUED_IN_FUTURE, jwt.JWT_ERR_ISSUER, jwt.JWT_ERR_AUDIENCE
println jwt.ErrorText(jwt.JWT_ERR_SUBJECT), "/", jwt.ErrorText(jwt.JWT_ERR_MISSING_CLAIM), "/", jwt.ErrorText(jwt.JWT_ERR_TYPE)
endGuard
struct Guard| Name | Type | Description |
|---|---|---|
secret | str = "" | The signing secret. |
opts | Options | The check options. |
cookie | str = "" | The name of a cookie the token is also read from; empty means the Authorization header only. |
realm | str = "api" | The name that goes in the WWW-Authenticate header. |
import jwt
import net.http
import net.router
mut guard := jwt.NewGuard("a-server-secret-only-the-server-knows")
func login(ctx: i64):
mut err := 0
mut c := jwt.NewClaims()
c.sub = "42"
jwt.SetStr(c, "scope", "read")
jwt.SetRaw(c, "roles", `["editor"]`)
token := jwt.Issue(guard, c, 3600, err)
jwt.SetSessionCookieInsecure(ctx, guard, token, 3600)
http.Ctx_text(ctx, token)
end
func me(ctx: i64):
mut t := jwt.Token {}
if jwt.Require(ctx, guard, t):
http.Ctx_text(ctx, "hello user " + t.claims.sub)
end
end
func admin(ctx: i64):
mut t := jwt.Token {}
if jwt.RequireScope(ctx, guard, "admin", t):
http.Ctx_text(ctx, "admin panel")
end
end
func edit(ctx: i64):
mut t := jwt.Token {}
if jwt.RequireClaim(ctx, guard, "roles", "editor", t):
http.Ctx_text(ctx, "editor")
end
end
func profile(ctx: i64):
mut t := jwt.Token {}
if jwt.RequireSubject(ctx, guard, http.Ctx_param(ctx, "id"), t):
http.Ctx_text(ctx, "your own profile")
end
end
func manual(ctx: i64):
mut err := 0
t := jwt.Check(ctx, guard, err)
if jwt.BearerToken(ctx).len() == 0 and jwt.TokenFrom(ctx, guard).len() == 0:
jwt.MissingCredential(ctx, guard)
else err != jwt.JWT_OK:
jwt.Unauthorized(ctx, guard, jwt.ErrorText(err))
else t.claims.sub == "0":
jwt.Forbidden(ctx, "the user is blocked")
else:
http.Ctx_text(ctx, "valid")
end
end
func logout(ctx: i64):
jwt.ClearSessionCookie(ctx, guard)
http.Ctx_text(ctx, "you are logged out")
end
func main:
guard.cookie = "session"
mut r := router.NewRouter()
router.Post(r, "/login", login)
router.Get(r, "/me", me)
router.Get(r, "/admin", admin)
router.Get(r, "/edit", edit)
router.Get(r, "/users/:id", profile)
router.Get(r, "/check", manual)
router.Post(r, "/logout", logout)
mut s := http.NewServer(19630)
http.Use(s, r)
_ := http.RunBackground(s)
base := "http://127.0.0.1:19630"
login_response := http.Post(base + "/login", "")
token := http.Body(login_response)
println http.CookieMap(login_response).get("session") == token
mut headers := http.NewHeaders()
headers.insert("Authorization", "Bearer " + token)
println jwt.ParseBearer("Bearer " + token) == token
ask(base, "/me", headers)
ask(base, "/admin", headers)
ask(base, "/edit", headers)
ask(base, "/users/42", headers)
ask(base, "/users/7", headers)
ask(base, "/check", headers)
ask(base, "/me", http.NewHeaders())
mut bad := http.NewHeaders()
bad.insert("Authorization", "Bearer x.y.z")
ask(base, "/check", bad)
mut cookie := http.NewHeaders()
cookie.insert("Cookie", "session=" + token)
ask(base, "/me", cookie)
mut access := ""
mut refresh := ""
mut err := 0
println jwt.IssuePair(guard, "42", 900, 86400, access, refresh, err), access != refresh
one := jwt.IssueFor(guard, "42", 60, err)
println jwt.Verify(one, guard.secret, guard.opts, err).claims.sub, guard.realm
end
func ask(base: str, path: str, headers: HashMap<str, str>):
response := http.GetWith(base + path, headers)
println path, http.Status(response), http.Body(response)
endConstants and variables
JWT_HS256
const JWT_HS256: str = "HS256"The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_HS384
const JWT_HS384: str = "HS384"The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_HS512
const JWT_HS512: str = "HS512"The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_OK
const JWT_OK: i32 = 0The example for this item is shown with the ones above: jwt-Sign.salam
JWT_ERR_MALFORMED
const JWT_ERR_MALFORMED: i32 = 1The example for this item is shown with the ones above: jwt-Sign.salam
JWT_ERR_BAD_BASE64
const JWT_ERR_BAD_BASE64: i32 = 2The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_BAD_JSON
const JWT_ERR_BAD_JSON: i32 = 3The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_ALG_UNSUPPORTED
const JWT_ERR_ALG_UNSUPPORTED: i32 = 4none for instance.The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_ALG_MISMATCH
const JWT_ERR_ALG_MISMATCH: i32 = 5The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_SIGNATURE
const JWT_ERR_SIGNATURE: i32 = 6The example for this item is shown with the ones above: jwt-Sign.salam
JWT_ERR_EXPIRED
const JWT_ERR_EXPIRED: i32 = 7The example for this item is shown with the ones above: jwt-Sign.salam
JWT_ERR_NOT_YET_VALID
const JWT_ERR_NOT_YET_VALID: i32 = 8nbf time has not arrived yet.The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_ISSUED_IN_FUTURE
const JWT_ERR_ISSUED_IN_FUTURE: i32 = 9The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_ISSUER
const JWT_ERR_ISSUER: i32 = 10The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_AUDIENCE
const JWT_ERR_AUDIENCE: i32 = 11The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_SUBJECT
const JWT_ERR_SUBJECT: i32 = 12The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_MISSING_CLAIM
const JWT_ERR_MISSING_CLAIM: i32 = 13The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_EMPTY_SECRET
const JWT_ERR_EMPTY_SECRET: i32 = 14The example for this item is shown with the ones above: jwt-Algorithms.salam
JWT_ERR_TYPE
const JWT_ERR_TYPE: i32 = 15JWT.The example for this item is shown with the ones above: jwt-Algorithms.salam
Functions
Has
func Has(t: Token, key: str): boolThe example for this item is shown with the ones above: jwt-Sign.salam
Raw
func Raw(t: Token, key: str): strThe example for this item is shown with the ones above: jwt-Sign.salam
Str
func Str(t: Token, key: str): strThe example for this item is shown with the ones above: jwt-Sign.salam
Int
func Int(t: Token, key: str): i64The example for this item is shown with the ones above: jwt-Sign.salam
Bool
func Bool(t: Token, key: str): boolThe example for this item is shown with the ones above: jwt-Sign.salam
HasAudience
func HasAudience(t: Token, want: str): boolThe example for this item is shown with the ones above: jwt-Sign.salam
ListHas
func ListHas(t: Token, key: str, want: str): boolThe example for this item is shown with the ones above: jwt-Sign.salam
HasScope
func HasScope(t: Token, want: str): boolscope claim, which separates scopes with spaces, has this scope.The example for this item is shown with the ones above: jwt-Sign.salam
SecondsRemaining
func SecondsRemaining(t: Token): i64The example for this item is shown with the ones above: jwt-Algorithms.salam
IsSupportedAlg
func IsSupportedAlg(alg: str): boolThe example for this item is shown with the ones above: jwt-Algorithms.salam
DigestSize
func DigestSize(alg: str): intThe example for this item is shown with the ones above: jwt-Algorithms.salam
ErrorText
func ErrorText(code: int): strThe example for this item is shown with the ones above: jwt-Sign.salam
NewClaims
func NewClaims(): ClaimsThe example for this item is shown with the ones above: jwt-Sign.salam
SetStr
func SetStr(c &: Claims, key: str, value: str)The example for this item is shown with the ones above: jwt-Sign.salam
SetInt
func SetInt(c &: Claims, key: str, value: i64)The example for this item is shown with the ones above: jwt-Sign.salam
SetBool
func SetBool(c &: Claims, key: str, value: bool)The example for this item is shown with the ones above: jwt-Sign.salam
SetRaw
func SetRaw(c &: Claims, key: str, raw: str)The example for this item is shown with the ones above: jwt-Sign.salam
ExpiresIn
func ExpiresIn(c &: Claims, seconds: i64)The example for this item is shown with the ones above: jwt-Algorithms.salam
NotBeforeIn
func NotBeforeIn(c &: Claims, seconds: i64)The example for this item is shown with the ones above: jwt-Algorithms.salam
ClaimsJSON
func ClaimsJSON(c: Claims): strThe example for this item is shown with the ones above: jwt-Sign.salam
DefaultOptions
func DefaultOptions(): OptionsThe example for this item is shown with the ones above: jwt-Sign.salam
OptionsFor
func OptionsFor(alg: str): OptionsThe example for this item is shown with the ones above: jwt-Algorithms.salam
Decode
func Decode(token: str, err &: int): TokenThe example for this item is shown with the ones above: jwt-Sign.salam
Verify
func Verify(token: str, secret: str, opts: Options, err &: int): TokenThe example for this item is shown with the ones above: jwt-Sign.salam
VerifyHS256
func VerifyHS256(token: str, secret: str, err &: int): TokenThe example for this item is shown with the ones above: jwt-Algorithms.salam
IsValid
func IsValid(token: str, secret: str, opts: Options): boolThe example for this item is shown with the ones above: jwt-Sign.salam
B64UrlEncodeBytes
func B64UrlEncodeBytes(p: void*, n: int): str=.The example for this item is shown with the ones above: jwt-Algorithms.salam
B64UrlEncode
func B64UrlEncode(s: str): strThe example for this item is shown with the ones above: jwt-Algorithms.salam
B64UrlDecode
func B64UrlDecode(s: str, ok &: bool): strThe example for this item is shown with the ones above: jwt-Algorithms.salam
SignJSON
func SignJSON(payload_json: str, alg: str, kid: str, secret: str, err &: int): strThe example for this item is shown with the ones above: jwt-Algorithms.salam
SignatureOf
func SignatureOf(signing_input: str, alg: str, secret: str): strThe example for this item is shown with the ones above: jwt-Algorithms.salam
SignWithKid
func SignWithKid(c: Claims, alg: str, kid: str, secret: str, err &: int): strSign, with a key id in the header; for rotating keys.The example for this item is shown with the ones above: jwt-Algorithms.salam
Sign
func Sign(c: Claims, alg: str, secret: str, err &: int): strThe example for this item is shown with the ones above: jwt-Sign.salam
SignHS256
func SignHS256(c: Claims, secret: str, err &: int): strThe example for this item is shown with the ones above: jwt-Sign.salam
SignHS384
func SignHS384(c: Claims, secret: str, err &: int): strThe example for this item is shown with the ones above: jwt-Algorithms.salam
SignHS512
func SignHS512(c: Claims, secret: str, err &: int): strThe example for this item is shown with the ones above: jwt-Algorithms.salam
Issue
func Issue(g: Guard, c: Claims, ttl: i64, err &: int): strThe example for this item is shown with the ones above: jwt-Algorithms.salam
IssueFor
func IssueFor(g: Guard, subject: str, ttl: i64, err &: int): strThe example for this item is shown with the ones above: jwt-Guard.salam
IssuePair
func IssuePair(g: Guard, subject: str, access_ttl: i64, refresh_ttl: i64, access &: str, refresh &: str, err &: int): boolkind claim tells them apart.The example for this item is shown with the ones above: jwt-Guard.salam
SetSessionCookie
func SetSessionCookie(ctx: i64, g: Guard, token: str, max_age: int)The example for this item is shown with the ones above: jwt-Guard.salam
SetSessionCookieInsecure
func SetSessionCookieInsecure(ctx: i64, g: Guard, token: str, max_age: int)The example for this item is shown with the ones above: jwt-Guard.salam
ClearSessionCookie
func ClearSessionCookie(ctx: i64, g: Guard)The example for this item is shown with the ones above: jwt-Guard.salam
NewGuard
func NewGuard(secret: str): GuardThe example for this item is shown with the ones above: jwt-Guard.salam
ParseBearer
func ParseBearer(header_value: str): strBearer ... header value.The example for this item is shown with the ones above: jwt-Guard.salam
BearerToken
func BearerToken(ctx: i64): strThe example for this item is shown with the ones above: jwt-Guard.salam
TokenFrom
func TokenFrom(ctx: i64, g: Guard): strThe example for this item is shown with the ones above: jwt-Guard.salam
Check
func Check(ctx: i64, g: Guard, err &: int): TokenThe example for this item is shown with the ones above: jwt-Guard.salam
Unauthorized
func Unauthorized(ctx: i64, g: Guard, reason: str)The example for this item is shown with the ones above: jwt-Guard.salam
MissingCredential
func MissingCredential(ctx: i64, g: Guard)The example for this item is shown with the ones above: jwt-Guard.salam
Forbidden
func Forbidden(ctx: i64, reason: str)The example for this item is shown with the ones above: jwt-Guard.salam
Require
func Require(ctx: i64, g: Guard, t &: Token): boolThe example for this item is shown with the ones above: jwt-Guard.salam
RequireScope
func RequireScope(ctx: i64, g: Guard, scope: str, t &: Token): boolThe example for this item is shown with the ones above: jwt-Guard.salam
RequireClaim
func RequireClaim(ctx: i64, g: Guard, key: str, value: str, t &: Token): boolThe example for this item is shown with the ones above: jwt-Guard.salam
RequireSubject
func RequireSubject(ctx: i64, g: Guard, subject: str, t &: Token): boolThe example for this item is shown with the ones above: jwt-Guard.salam