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
rust
use axum::{ body::Bytes, extract::State, http::{HeaderMap, StatusCode},
Verifying Stripe webhook signatures in Axum
hmac
webhooks
constant-time-comparison
Intermediate
8 steps
go
package pipeline import ( "context"
A cancelable worker pool in Go
concurrency
channels
worker-pool
Advanced
8 steps
php
<?php namespace App\Http\Controllers\Auth;
Rate-limited login in Laravel
authentication
rate-limiting
validation
Intermediate
9 steps
rust
use axum::{ body::Body, extract::Request, http::{header, StatusCode},
How bearer-auth middleware works in Axum
middleware
authentication
request extensions
Intermediate
7 steps
typescript
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { AsyncValidator,
How an async username validator works in Angular
async-validation
reactive-forms
rxjs
Intermediate
6 steps
javascript
const { validationResult, matchedData } = require('express-validator'); function validate(schema) { const runners = schema.map((rule) => rule.run.bind(rule));
A reusable validation middleware in Express
middleware
validation
higher-order-functions
Intermediate
8 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.