How Authentication Secures Zakirullin Files API Endpoints: Token-Based Implementation
The Zakirullin Files API protects all sync endpoints using a stateless token-based authentication scheme where permanent tokens are hashed with SHA-256 and a server-side salt, requiring clients to present valid credentials via HTTP-only cookies or Authorization headers.
The Zakirullin Files project implements a robust security layer for its synchronization API. Understanding how authentication secures Zakirullin Files API endpoints reveals a design that prioritizes token confidentiality and stateless validation. This guide examines the token issuance flow, storage mechanisms, and request validation logic implemented in the zakirullin/files.md repository.
Token-Based Authentication Architecture
The service secures sensitive endpoints like /syncFile and /syncFilenames through a permanent token system managed by the tokenMiddleware. Unlike session-based approaches, this implementation generates cryptographically random tokens that identify users without maintaining server-side session state. Each token undergoes salting and hashing before persistence, ensuring that even disk-level compromises do not expose valid credentials.
How Tokens Are Issued and Stored
One-Time Token Exchange
The authentication flow begins at the /token endpoint, handled by the IssueToken function in server/sync/tokens.go. Clients initiate the process by POSTing a JSON payload containing a one-time token. Upon validation, the server invokes genToken() to create a new permanent credential, immediately hashing it via hashToken before storage.
// Token issuance handler (server/sync/tokens.go)
func IssueToken(w http.ResponseWriter, r *http.Request) {
// expects JSON: {"oneTimeToken":"<otp>"}
permanentToken, ok := issueNewPermanentToken(r)
if !ok {
http.Error(w, "Invalid or expired token", http.StatusUnauthorized)
return
}
// set the token as a secure cookie
setAuthCookie(w, permanentToken)
// also return it in the JSON body
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"token": permanentToken})
}
Secure Hash Storage
Permanent tokens are never stored in plaintext. The hashToken function applies SHA-256 hashing combined with config.ServerCfg.TokensSalt before writing to disk via tokens.Write. This salted-hash approach ensures that token verification occurs through constant-time comparison of hashed values, mitigating rainbow table attacks.
How Clients Authenticate API Requests
Cookie-Based Transmission
The primary authentication method uses an HTTP-only, Secure, SameSite=None cookie named token. The setAuthCookie function configures these flags during the /token exchange, preventing JavaScript access and ensuring transmission only over HTTPS connections. This mechanism protects against XSS attacks while allowing cross-origin synchronization requests.
Header-Based Fallback
When cookies are unavailable, clients may present the token in the Authorization request header. The tokenMiddleware checks for this header as a secondary validation method. Notably, when authentication succeeds via header, the middleware automatically migrates the token to a cookie for subsequent requests, optimizing future validation performance.
// Middleware protecting API endpoints (server/sync/tokens.go)
func tokenMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 1️⃣ Try the cookie first
var token string
fromCookie := false
if c, err := r.Cookie(AuthCookieName); err == nil && c.Value != "" {
token = c.Value
fromCookie = true
}
// 2️⃣ Fallback to Authorization header
if token == "" {
token = r.Header.Get("Authorization")
}
// 3️⃣ Validate the token
userID, ok := findUserID(token)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 4️⃣ Migrate header‑sent tokens to a cookie
if !fromCookie {
setAuthCookie(w, token)
}
// 5️⃣ Pass the authenticated user ID downstream
ctx := context.WithValue(r.Context(), "userID", userID)
next(w, r.WithContext(ctx))
}
}
The Token Middleware Validation Flow
Every protected route registers with tokenMiddleware wrapping the handler, as seen in the route registration logic (typically located in server/sync/webserver.go or similar). The middleware extracts credentials, validates them against disk-stored hashes via findUserID and tokens.Read, and injects the userID into the request context. Invalid or missing tokens trigger a 401 Unauthorized response and may activate temporary IP blocking mechanisms detailed in server/sync/tokens_dbg.go.
// Example protected endpoint registration
func registerRoutes(r *mux.Router) {
// All sync endpoints require a valid token
r.HandleFunc("/syncFile", corsMiddleware(panicMiddleware(tokenMiddleware(gzipMiddleware(SyncFile)))))
r.HandleFunc("/syncFilenames", corsMiddleware(panicMiddleware(tokenMiddleware(gzipMiddleware(SyncFilenames)))))
// Token issuance itself is public (POST only)
r.HandleFunc("/token", corsMiddleware(panicMiddleware(IssueToken)))
}
Security Implementation Details
The authentication system relies on configuration parameters defined in server/config/*, specifically ServerCfg.TokensSalt and ServerCfg.TokensDir. These control the hashing entropy and storage location respectively. The use of genToken() ensures cryptographically random token generation, while the middleware's dual-source extraction (cookie优先, header fallback) provides flexibility without compromising security boundaries.
Summary
- Token-based scheme: All API endpoints except
/tokenrequire a valid permanent token validated bytokenMiddleware. - Secure storage: Tokens are stored as SHA-256 salted hashes via
hashTokenandtokens.Write, never in plaintext. - Dual presentation: Clients authenticate using HTTP-only Secure cookies or the Authorization header, with automatic migration to cookies.
- Context injection: Successful validation injects
userIDinto the request context for downstream handlers. - Failure handling: Invalid tokens return 401 status codes and may trigger IP-based rate limiting.
Frequently Asked Questions
What type of authentication does Zakirullin Files use?
The implementation uses stateless token-based authentication. Clients obtain permanent tokens by exchanging one-time tokens at the /token endpoint, then present these tokens via cookies or headers for all subsequent API calls to endpoints like /syncFile.
How are tokens stored securely on the server?
Tokens are hashed using SHA-256 with a server-side salt (config.ServerCfg.TokensSalt) before persistence. The hashToken function in server/sync/tokens.go processes tokens before tokens.Write stores them in the configured TokensDir, ensuring that raw token values never exist on disk.
What happens if a token is invalid or missing?
The tokenMiddleware returns a 401 Unauthorized status code when findUserID fails to locate a matching hashed token. Repeated failures may trigger temporary IP blocking mechanisms implemented in the debug instrumentation file (server/sync/tokens_dbg.go), protecting against brute-force attempts.
Can clients use headers instead of cookies for authentication?
Yes, while HTTP-only cookies are the preferred method, clients may present tokens in the Authorization header as a fallback. The middleware automatically detects header-based authentication and migrates the token to a secure cookie via setAuthCookie for subsequent requests, maintaining security while improving user experience.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →