go
50 lines · 7 steps
Building a JWT auth middleware in Gin
A Gin middleware that validates a Bearer JWT and hands its claims to downstream handlers.
Explained by
highlit
1package auth
2
3import (
4 "net/http"
5 "strings"
6
7 "github.com/gin-gonic/gin"
8 "github.com/golang-jwt/jwt/v5"
9)
10
11type Claims struct {
12 UserID string `json:"uid"`
13 Email string `json:"email"`
14 Role string `json:"role"`
15 jwt.RegisteredClaims
16}
17
18func JWTAuth(secret []byte) gin.HandlerFunc {
19 return func(c *gin.Context) {
20 header := c.GetHeader("Authorization")
21 if header == "" {
22 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"})
23 return
24 }
25
26 raw, ok := strings.CutPrefix(header, "Bearer ")
27 if !ok {
28 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid authorization scheme"})
29 return
30 }
31
32 claims := &Claims{}
33 token, err := jwt.ParseWithClaims(raw, claims, func(t *jwt.Token) (interface{}, error) {
34 if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
35 return nil, jwt.ErrSignatureInvalid
36 }
37 return secret, nil
38 })
39 if err != nil || !token.Valid {
40 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired token"})
41 return
42 }
43
44 c.Set("userID", claims.UserID)
45 c.Set("email", claims.Email)
46 c.Set("role", claims.Role)
47 c.Set("claims", claims)
48 c.Next()
49 }
50}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Middleware that closes over config (like a secret) keeps handlers free of setup boilerplate.
- 2Always verify the token's signing method to defend against algorithm-confusion attacks.
- 3Storing parsed claims in the request context lets downstream handlers read the user without re-parsing.
Related explainers
ruby
class ApplicationController < ActionController::Base ALLOWED_REDIRECT_HOSTS = [nil, ENV.fetch("APP_HOST", "app.example.com")].freeze def store_return_to(location = request.fullpath)
Safe post-login redirects in Rails
open-redirect
session
authentication
Intermediate
9 steps
go
package middleware import ( "compress/gzip"
How gzip HTTP middleware works in Go
middleware
compression
object-pooling
Intermediate
7 steps
go
package events import ( "net/http"
Two-pass JSON dispatch in Gin
polymorphic-json
request-binding
validation
Intermediate
8 steps
javascript
const FOCUSABLE = [ 'a[href]', 'button:not([disabled])', 'input:not([disabled])',
How to trap keyboard focus in a dialog
accessibility
dom
event-handling
Intermediate
8 steps
go
package handlers type ListFilters struct { Status string `form:"status" binding:"omitempty,oneof=active archived all"`
Cross-field query validation in Gin
validation
struct-tags
query-binding
Intermediate
9 steps
rust
use std::collections::HashMap; #[derive(Debug)] pub struct RequestHead {
Parsing an HTTP request head in Rust
parsing
error-handling
iterators
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-jwt-auth-middleware-in-gin-explained-go-e3b6/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.