Dynamic Routes and Cookie Authentication in net/http: Implementing DeadDrop
Continuing our series on pure net/http, we implement dynamic routing for the DeadDrop service. Users will be able to view secrets via links like /secret/{id}. The standard mux doesn't support path parameters until Go 1.22, so we write our own parser.
The handler is registered at /secret/ with a trailing slash—this captures all child paths. We extract the ID manually from r.URL.Path.
func extractIDFromPath(path string) (string, bool) {
const prefix = "/secret/"
if !strings.HasPrefix(path, prefix) {
return "", false
}
id := strings.TrimPrefix(path, prefix)
if id == "" || strings.Contains(id, "/") {
return "", false
}
return id, true
}
The function checks the prefix, trims it, and excludes invalid IDs (empty or containing /). This provides basic protection against malformed requests.
SecretHandler with DI
We create a handler struct for dependency injection:
type SecretHandler struct {
// placeholder for DI
}
func NewSecretHandler() *SecretHandler {
return &SecretHandler{}
}
func (h *SecretHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
id, ok := url.ExtractIDFromPath(r.URL.Path)
if !ok {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte("Requested secret with ID: " + id))
}
Register it in router.go: mux.Handle("/secret/", secretHandler). Test edge cases:
/secret/12345/→ 404/secret/→ 404/secret/axQ/233→ 404/secret/ad31H234→ OK with ID
Use Case for Retrieving a Secret
Define the request and response:
type GetSecretRequest struct {
ID string
Password string
}
type GetSecretResponse struct {
ID string
Message string
FileName string
FileExt string
HasFile bool
ExpiresAt time.Time
}
Use case logic:
- Retrieve the secret from storage
- Check expiration (
time.After()), delete expired ones - Verify password via
CheckPasswordHash - Return data or an error
Cookie Authentication
To protect file downloads, we implement authentication via cookies. After successful password verification, set a session cookie with an encrypted access token.
- Advantages: stateless, scalable, no session storage
- Cookie parameters: HttpOnly, Secure, SameSite=Strict
When requesting /download/{id}:
- Extract ID from the path
- Read the
auth_tokencookie - Decode and verify the token (ID + password hash + nonce)
- If valid—serve the file
Access Token Structure
The token is formed as base64(ID + ":" + hash + ":" + nonce), where:
hash = HMAC-SHA256(ID + password, secretKey)nonce—a one-time number to protect against replay attacks
Example validation:
func validateDownloadToken(id, token string) bool {
// decode token
// extract hash and nonce
// recalculate HMAC
// compare
return true // or false
}
Full Secret Workflow
- Creation: POST
/create→ generate ID, hash password, save - Viewing: GET
/secret/{id}→ password input form - Authentication: POST password → cookie + redirect
- Download: GET
/download/{id}→ check cookie → file - Cleanup: TTL expired → deletion
UI prototype: page with password field, "Open" button. After success—content + file link.
Routing Testing
| Test Path | Expectation | Result |
|-----------|-------------|--------|
| /secret/123/ | 404 | 404 |
| /secret/ | 404 | 404 |
| /secret/id/with/slash | 404 | 404 |
| /secret/valid123 | OK: "ID: valid123" | OK |
Edge cases cover parser security.
Key Points
- Dynamic routes: manual parsing of
r.URL.Pathwithout third-party routers - Security: ID validation, slash checks, TTL for secrets
- Authentication: cookie with HMAC token instead of sessions
- Architecture: use case + DI, clean logic outside handlers
- Scalability: stateless, ready for clustering
— Editorial Team
No comments yet.