go-chi/jwtauth

repository·master·Indexed 20 days ago

https://github.com/go-chi/jwtauth

A lightweight JWT authentication middleware for Go HTTP services, optimized for the chi router. It provides a two-step middleware pattern using Verifier to extract and validate tokens from headers or cookies, and Authenticator to enforce access with 401 Unauthorized responses. The library includes tools for encoding and decoding tokens, retrieving claims from request contexts via FromContext, and supporting custom token lookup sequences.

Tokens
2.5K
Snippets
12
Records
13
Agent score
21%

What's inside go-chi/jwtauth

  1. How jwtauth middleware works

    master

    The jwtauth package uses a two-step middleware pattern to handle JWT authentication:

    1. jwtauth.Verifier: This middleware extracts the token from the request, decodes it, verifies the signature, and validates expiration. It then injects the resulting jwt.Token and any potential errors into the request context using the keys jwtauth.TokenCtxKey and jwtauth.ErrorCtxKey. The Verifier always calls the next handler in the chain.

    2. Authentication Handler: Since the Verifier always continues the chain, you must use a second handler to decide how to respond to the user.

      • Use jwtauth.Authenticator for a default behavior that returns a 401 Unauthorized plain-text response for invalid tokens and allows valid tokens to proceed.
      • Implement a custom handler if you need specific response formats (e.g., JSON error bodies).

    By default, the Verifier looks for tokens in this order:

    1. Authorization: BEARER <token> header
    2. jwt Cookie value
    r.Use(jwtauth.Verifier(tokenAuth))
    r.Use(jwtauth.Authenticator(tokenAuth))
  2. Implement JWT authentication in a chi router

    master

    To protect routes, initialize a jwtauth.JWTAuth instance, then apply the Verifier and Authenticator middlewares to a route group. You can retrieve claims from the request context using jwtauth.FromContext(r.Context()).

    package main
    
    import (
    	"fmt"
    	"net/http"
    
    	"github.com/go-chi/chi/v5"
    	"github.com/go-chi/jwtauth/v5"
    )
    
    var tokenAuth *jwtauth.JWTAuth
    
    func init() {
    	// Initialize with algorithm and secret
    	tokenAuth = jwtauth.New("HS256", []byte("secret"), nil)
    }
    
    func router() http.Handler {
    	r := chi.NewRouter()
    
    	// Protected routes
    	r.Group(func(r chi.Router) {
    		// 1. Seek, verify and validate JWT tokens
    		r.Use(jwtauth.Verifier(tokenAuth))
    
    		// 2. Handle valid / invalid tokens
    		r.Use(jwtauth.Authenticator(tokenAuth))
    
    		r.Get("/admin", func(w http.ResponseWriter, r *http.Request) {
    		// Retrieve claims from context
    		_, claims, _ := jwtauth.FromContext(r.Context())
    		w.Write([]byte(fmt.Sprintf("protected area. hi %v", claims["user_id"])))
    	})
    	})
    
    	return r
    }
  3. Retrieve JWT claims from context

    master

    Once the jwtauth.Verifier has processed a request, you can access the decoded token and its claims within your HTTP handlers using jwtauth.FromContext(r.Context()).

    _, claims, err := jwtauth.FromContext(r.Context())
    if err != nil {
        // handle error
    }
    // use claims["key"]
  4. Use custom token lookup sequences

    master
    While the default Verifier looks for tokens in the Authorization header and then in cookies, you can define a custom lookup sequence by calling the Verify middleware instantiator directly. The default instantiation is equivalent to Verify(ja, TokenFromHeader, TokenFromCookie).
  5. Retrieve token and claims from context

    master

    After Verifier has run, you can access the parsed JWT token and its claims within your handlers using FromContext. This function returns the jwt.Token, a map[string]interface{} containing the claims, and any error encountered during verification.

    func MyHandler(w http.ResponseWriter, r *http.Request) {
        token, claims, err := jwtauth.FromContext(r.Context())
        if err != nil {
            // Handle error (e.g., token expired)
            return
        }
        
        // Access a specific claim
        userID := claims["sub"]
        fmt.Fprintf(w, "Hello, %v", userID)
    }
  6. Encode and Decode JWT tokens

    master

    You can manually manage tokens using the Encode and Decode methods on a *JWTAuth instance.

    • Encode(claims map[string]interface{}): Creates a new signed JWT string from a map of claims.
    • Decode(tokenString string): Parses and verifies a raw JWT string.
    // Encoding
    claims := map[string]interface{}{"sub": "12345", "name": "John Doe"}
    token, tokenString, err := ja.Encode(claims)
    
    // Decoding
    token, err := ja.Decode(tokenString)
  7. Initialize JWTAuth with New()

    master

    To use jwtauth, you must first create a *JWTAuth instance using New(). This instance holds your signing algorithm, keys, and validation options.

    If you are using asymmetric algorithms (like RSA or ECDSA), provide the public key as the verifyKey. For symmetric algorithms (like HMAC), the signKey is used for both signing and verification.

    // Example for HMAC (Symmetric)
    ja := jwtauth.New("HS256", []byte("secret"), nil)
    
    // Example for RSA (Asymmetric)
    ja := jwtauth.New("RS256", privateKey, publicKey)
  8. Custom token extraction with Verify()

    master

    If you need to support token extraction from sources other than the default (Header and Cookie), use Verify() instead of Verifier(). You can pass custom functions that satisfy the signature func(r *http.Request) string to define where to look for the token.

    // Example: Look in Query param first, then Header
    r.Use(jwtauth.Verify(ja, jwtauth.TokenFromQuery, jwtauth.TokenFromHeader))
  9. Use Verifier middleware to extract and verify tokens

    master

    The Verifier middleware is responsible for finding a JWT in an incoming request and verifying its signature and validity. It does not block the request; instead, it attaches the resulting jwt.Token and any error to the request context.

    Verifier searches for tokens in this order:

    1. Authorization: BEARER <token> header
    2. Cookie named jwt

    It always calls the next handler in the chain. You must follow Verifier with either Authenticator or a custom handler to decide how to respond to invalid tokens.

    // Standard usage with default token locations (Header then Cookie)
    r.Use(jwtauth.Verifier(ja))
  10. Use Authenticator middleware to enforce access

    master

    The Authenticator middleware is used to enforce authentication. It checks the request context for a valid token provided by a preceding Verifier middleware.

    If the token is missing or invalid (based on the error stored in the context), Authenticator returns a 401 Unauthorized response and stops the chain. If the token is valid, it calls the next handler.

    // Typical middleware chain
    r.Use(jwtauth.Verifier(ja))
    r.Use(jwtauth.Authenticator(ja))
    
    r.Get("/protected", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Access granted!"))
    })
  11. Helper functions for managing JWT claims

    master

    The package provides several helpers to simplify setting standard JWT claims like iat (Issued At), exp (Expiration), and nbf (Not Before) using Unix timestamps.

    claims := map[string]interface{}{}
    
    // Set 'iat' to current time
    jwtauth.SetIssuedNow(claims)
    
    // Set 'exp' to 1 hour from now
    jwtauth.SetExpiryIn(time.Hour)
    
    // Set 'exp' to a specific time
    jwtauth.SetExpiry(claims, someTime)
  12. Reference: JWT Authentication Errors

    master

    The following error variables are used to identify specific authentication failures when calling FromContext or VerifyToken.

    var (
    	ErrUnauthorized = errors.New("token is unauthorized")
    	ErrExpired      = errors.New("token is expired")
    	ErrNBFInvalid   = errors.New("token nbf validation failed")
    	ErrIATInvalid   = errors.New("token iat validation failed")
    	ErrNoTokenFound = errors.New("no token found")
    	ErrAlgoInvalid  = errors.New("algorithm mismatch")
    )