go
52 lines · 8 steps
Verifying signed URLs with Gin middleware
A Gin middleware that validates HMAC-signed, time-limited download links before letting a request through.
Explained by
highlit
1package middleware
2
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/hex"
7 "net/http"
8 "strconv"
9 "time"
10
11 "github.com/gin-gonic/gin"
12)
13
14func VerifySignedURL(secret []byte) gin.HandlerFunc {
15 return func(c *gin.Context) {
16 expiresRaw := c.Query("expires")
17 sig := c.Query("signature")
18 if expiresRaw == "" || sig == "" {
19 c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing signature parameters"})
20 return
21 }
22
23 expires, err := strconv.ParseInt(expiresRaw, 10, 64)
24 if err != nil {
25 c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid expires"})
26 return
27 }
28
29 if time.Now().Unix() > expires {
30 c.AbortWithStatusJSON(http.StatusGone, gin.H{"error": "link has expired"})
31 return
32 }
33
34 provided, err := hex.DecodeString(sig)
35 if err != nil {
36 c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "malformed signature"})
37 return
38 }
39
40 payload := c.Param("file") + "?expires=" + expiresRaw
41 mac := hmac.New(sha256.New, secret)
42 mac.Write([]byte(payload))
43 expected := mac.Sum(nil)
44
45 if !hmac.Equal(provided, expected) {
46 c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "invalid signature"})
47 return
48 }
49
50 c.Next()
51 }
52}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Signing the URL with HMAC lets the server trust a link it handed out without storing any per-link state.
- 2Baking an expiry into the signed payload turns any link into a self-invalidating, time-limited grant.
- 3Comparing MACs with a constant-time check avoids leaking secret information through timing side channels.
Related explainers
javascript
import { createContext, useContext, useReducer, useCallback, useEffect } from 'react'; const AuthContext = createContext(null);
Building an auth context in React
context
usereducer
authentication
Intermediate
8 steps
go
package dedup import ( "hash/fnv"
A rolling Bloom filter deduper in Go
bloom-filter
deduplication
concurrency
Advanced
7 steps
rust
use axum::{ extract::{FromRequestParts, Path, Query}, http::{request::Parts, StatusCode}, response::{IntoResponse, Redirect},
Signed download links as an Axum extractor
hmac
custom-extractor
authentication
Advanced
9 steps
rust
use axum::{ extract::{FromRef, FromRequestParts}, http::{header, request::Parts, StatusCode}, RequestPartsExt,
How a JWT extractor works in Axum
jwt
authentication
extractors
Intermediate
8 steps
go
package server import ( "net/http"
Rate limiting HTTP handlers with a token bucket
rate-limiting
token-bucket
middleware
Advanced
7 steps
go
package middleware import ( "fmt"
How a panic-recovery middleware works in Gin
middleware
panic-recovery
error-reporting
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/verifying-signed-urls-with-gin-middleware-explained-go-4b04/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.