Learn Chi - Authentication & Authorization
Series/Learn Chi/Episode 13
Episode 13 of 23

Learn Chi - Authentication & Authorization

This episode builds the authentication and authorization layer: JWT with golang-jwt as middleware, chi's built-in BasicAuth, session cookies, and the refresh token pattern. You will also learn role-based RBAC middleware and OAuth2 and OpenID Connect integration with Keycloak.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Authentication answers "who are you"; authorization answers "what are you allowed to do". Episode 13 builds both on top of chi with patterns widely used in production: JWT for API tokens, Basic Auth for simple admin access, session cookies for web, and RBAC for role-based control. Because all of these mechanisms are func(http.Handler) http.Handler middleware, you can attach them to the whole router, a subrouter, or a single route — exactly the pattern you learned in episode 6.

JWT as Middleware

Generating Tokens

Install golang-jwt
go get github.com/golang-jwt/jwt/v5
Creating a JWT token
func issueToken(userID int, role string) (string, error) {
    claims := jwt.MapClaims{
        "sub":  userID,
        "role": role,
        "exp":  time.Now().Add(time.Hour).Unix(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(jwtSecret))
}

jwt.NewWithClaims(jwt.SigningMethodHS256, claims) creates a token that carries the subject (user ID), role, and expiry time. token.SignedString(...) signs it with the secret.

Verify Middleware

JWT verify middleware
func authenticate(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        header := req.Header.Get("Authorization")
        tokenStr, _ := strings.CutPrefix(header, "Bearer ")
 
        token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (any, error) {
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, jwt.ErrSignatureInvalid
            }
            return []byte(jwtSecret), nil
        })
        if err != nil || !token.Valid {
            http.Error(w, "token tidak valid",
                http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, req)
    })
}

strings.CutPrefix(header, "Bearer ") strips the Bearer scheme from the header. The middleware rejects the request with 401 Unauthorized when the token is malformed or expired.

Built-in BasicAuth

Simple Authentication

For internal or admin areas, middleware.BasicAuth is enough:

Built-in basic auth
r.Route("/admin", func(admin chi.Router) {
    admin.Use(middleware.BasicAuth("chi-service",
        map[string]string{"admin": "s3cret"}))
    admin.Get("/stats", statsHandler)
})

middleware.BasicAuth("chi-service", map[string]string{...}) validates the username and password from the Authorization header. Every route inside the /admin subrouter is protected.

Session Cookies and Refresh Tokens

Session Cookies

For web apps, store the identity in a signed cookie:

Set session cookie
func loginHandler(w http.ResponseWriter, req *http.Request) {
    token, _ := issueToken(userID, role)
    http.SetCookie(w, &http.Cookie{
        Name:  "session",
        Value: token,
        Path:  "/",
        HttpOnly: true,
        Secure:   true,
        SameSite: http.SameSiteStrictMode,
    })
    writeJSON(w, http.StatusOK, map[string]string{"login": "sukses"})
}

http.Cookie{HttpOnly: true, Secure: true} prevents access from JavaScript and restricts the cookie to HTTPS only. The cookie is read in middleware to obtain the token without a manual header.

Refresh Tokens

Short-lived access tokens (e.g., 15 minutes) and long-lived refresh tokens are stored safely:

Refresh endpoint
r.Post("/auth/refresh", func(w http.ResponseWriter, req *http.Request) {
    newAccess, _ := issueToken(userID, role)
    writeJSON(w, http.StatusOK,
        map[string]string{"access_token": newAccess})
})

r.Post("/auth/refresh", Handle(...)) issues a new access token after the refresh token is verified — this pattern minimizes how long a leaked token remains valid.

RBAC Middleware

Role-Based Authorization

RBAC middleware
u, ok := currentUser(req)
if !ok || u.Role != role {
    http.Error(w, "akses ditolak", http.StatusForbidden)
    return
}

requireRole("admin") wraps routes that only admins may access. This middleware is mounted after authenticate, so the user is already identified before their role is checked.

OAuth2 and OpenID Connect

Keycloak Integration

For enterprise SSO, chi can accept tokens from an identity provider:

Verify token from Keycloak
jwksURL := "https://auth.example.com/realms/myrealm/protocol/openid-connect/certs"
 
provider, _ := oidc.NewProvider(context.Background(), jwksURL)
verifier := provider.Verifier(&oidc.Config{
    ClientID: "chi-service",
})

provider.Verifier(...) validates the JWT signature against public keys from Keycloak. A valid ID token automatically carries the sub, email, and scope claims for further authorization.

Scopes Inside the Token

The scope claim carries granular permissions:

Check scope
scopes := strings.Fields(tokenScopes(req))
if !slices.Contains(scopes, scope) {
    http.Error(w, "scope tidak dimiliki",
        http.StatusForbidden)
    return
}

slices.Contains(scopes, scope) checks whether the token has the requested scope. The scopes pattern lets an API serve clients with different permissions.

Conclusion

Key takeaways:

  • JWTs are generated with golang-jwt and verified as middleware.
  • middleware.BasicAuth is enough for simple admin areas.
  • Session cookies are set with HttpOnly and Secure.
  • Refresh tokens shorten the lifetime of access tokens.
  • requireRole and requireScope add an authorization layer.
  • OIDC from Keycloak is validated through JWKS public keys.

In the next episode 14 we strengthen our defenses: security middleware and input hardening — CORS with rs/cors, CSP and HSTS security headers, CSRF, body limits, rate limiting, input validation, and preventing injection and SSRF with safe X-Forwarded-For handling.

Learn Chi - Authentication & Authorization | Learn Chi