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

Walkthrough

Space play step click any line
Three takeaways
  1. 1Signing the URL with HMAC lets the server trust a link it handed out without storing any per-link state.
  2. 2Baking an expiry into the signed payload turns any link into a self-invalidating, time-limited grant.
  3. 3Comparing MACs with a constant-time check avoids leaking secret information through timing side channels.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Verifying signed URLs with Gin middleware — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code