JWT middleware

Verify Keycloak access tokens and authorise by realm role — without ever crashing on an unexpected token.

Import
github.com/HiWay-Media/hwm-go-utils/api/middlewares
Algorithms
RS256, RS384, RS512 only
Checks
signature · exp (required) · iss and aud (opt-in) · realm roles
  1. Usage
    1. The public key
  2. Options
  3. What gets rejected
  4. Roles
  5. Reading claims

Usage

auth := middlewares.JwtProtected(publicKey,
	middlewares.WithIssuer("https://sso.example.com/realms/media"),
	middlewares.WithAudience("media-api"),
)

app.Get("/me", auth, func(c *fiber.Ctx) error {
	claims, _ := middlewares.GetTokenClaims(c)
	return c.JSON(fiber.Map{
		"user":     middlewares.GetCurrentLoggedUserUUIDFromJwt(claims),
		"email":    middlewares.GetCurrentLoggedUserEmailFromJwt(claims),
		"customer": middlewares.GetCustomerIdFromJwt(claims),
	})
})

app.Delete("/channels/:id", auth, middlewares.RoleCheck([]string{"admin", "operator"}), deleteChannel)

The public key

Copy it from Keycloak → Realm settings → Keys → RS256 → Public key. Both forms work:

  • the bare base64 string shown by the console (MIIBIjANBgkqh…);
  • a full PEM block (-----BEGIN PUBLIC KEY----- …).

The key is parsed once, when JwtProtected is called. If it is invalid, the problem is logged at startup and every request is rejected with 401 — the middleware fails closed.

Options

Option Checks When to use
WithIssuer(iss) iss equals iss Always in production: rejects tokens minted by other realms that share a key
WithAudience(aud) aud contains aud When the Keycloak client has an audience mapper for your API

By default Keycloak access tokens often carry only aud: "account". Add an Audience protocol mapper to the client before enabling WithAudience, or every token will be refused.

What gets rejected

Token Result
Missing Authorization header, missing Bearer prefix, empty token 401 malformed token
Bad signature, signed with another key 401 invalid token
alg: none, HMAC algorithms (including HS256 signed with the public key) 401 invalid token
Expired, or no exp claim at all 401 invalid token
Wrong issuer / audience (when configured) 401 invalid token

The reason is written to the server log; the client only sees invalid token.

Roles

RoleCheck(roles) lets the request through when the token’s realm_access.roles contains at least one of roles.

Situation Status
At least one role present handler runs
None present, or no realm_access claim 403 Missing role …
RoleCheck used without JwtProtected before it 401 unauthorized
Empty role list handler runs

Reading claims

GetTokenClaims(c) returns the verified jwt.MapClaims. The getters never panic: a missing or mistyped claim returns the zero value.

Getter Claim Missing →
GetCurrentLoggedUserUUIDFromJwt sub ""
GetCurrentLoggedUserEmailFromJwt email ""
GetCustomerNameFromJwt customer_name ""
GetCustomerIdFromJwt customer_id 0
GetClientIdFromJwt customer_name + _client "_client"
GetRolesListFromJwt realm_access.roles error

customer_name and customer_id are custom claims: add them with user-attribute mappers in Keycloak.