How CasaOS Implements JWT-Based Authentication and Token Management

CasaOS validates every API request using JSON Web Tokens (JWT) processed through Echo middleware that verifies ECDSA signatures against a runtime public key and injects the user ID into request headers for downstream handlers.

CasaOS is an open-source home cloud platform developed by IceWhaleTech that secures its REST APIs using stateless JWT-based authentication. The implementation leverages the Echo web framework alongside cryptographic utilities from the CasaOS-Common library to enforce token validation on all /v1 and /v2 endpoints.

JWT Middleware Integration in the Echo Router

CasaOS centralizes authentication logic in dedicated router files, applying middleware during route group initialization. This approach ensures consistent protection across the entire API surface without requiring individual handler modifications.

Token Validation Pipeline in route/v1.go and route/v2.go

The router configuration in route/v1.go (lines 44-56) and route/v2.go (lines 71-84) applies echo_middleware.JWTWithConfig to the API groups. The middleware configuration defines a custom ParseTokenFunc that delegates cryptographic validation to jwt.Validate from the CasaOS-Common package.

The configuration includes three critical components:

  • Skipper function: Returns true for localhost requests (::1 or 127.0.0.1), bypassing authentication for local development
  • ParseTokenFunc: Validates the token signature using an ECDSA public key retrieved via external.GetPublicKey(config.CommonInfo.RuntimePath)
  • TokenLookupFuncs: Extracts tokens from either the Authorization header (Bearer scheme) or the token query parameter
// route/v1.go – lines 44-56
v1Group.Use(echo_middleware.JWTWithConfig(echo_middleware.JWTConfig{
    Skipper: func(c echo.Context) bool {
        return c.RealIP() == "::1" || c.RealIP() == "127.0.0.1"
    },
    ParseTokenFunc: func(token string, c echo.Context) (interface{}, error) {
        valid, claims, err := jwt.Validate(
            token,
            func() (*ecdsa.PublicKey, error) { 
                return external.GetPublicKey(config.CommonInfo.RuntimePath) 
            })
        if err != nil || !valid {
            return nil, echo.ErrUnauthorized
        }
        c.Request().Header.Set("user_id", strconv.Itoa(claims.ID))
        return claims, nil
    },
    TokenLookupFuncs: []echo_middleware.ValuesExtractor{
        func(ctx echo.Context) ([]string, error) {
            if len(ctx.Request().Header.Get(echo.HeaderAuthorization)) > 0 {
                return []string{ctx.Request().Header.Get(echo.HeaderAuthorization)}, nil
            }
            return []string{ctx.QueryParam("token")}, nil
        },
    },
}))

Asymmetric Key Verification

The validation process uses asymmetric cryptography rather than shared secrets. The jwt.Validate function receives a callback returning an *ecdsa.PublicKey loaded from the runtime path. This design eliminates the need to distribute private signing keys to the API server, maintaining a secure separation between token issuance and verification.

Token Extraction and Claims Processing

Once the middleware validates the signature, it processes the JWT claims to identify the requesting user and make that context available to business logic.

Authorization Header and Query Parameter Support

The middleware supports dual token sources through the TokenLookupFuncs array. It first checks the Authorization header for a Bearer token. If the header is absent, it falls back to the token query parameter, enabling authentication in scenarios such as WebSocket connections or direct browser links where header manipulation is difficult.

User ID Propagation to Downstream Handlers

After successful validation, the middleware extracts the ID field from the JWT claims and injects it into the request header as user_id. This allows handlers to access the authenticated user identifier via c.Request().Header.Get("user_id") without re-parsing the token. The claims object includes standard JWT fields such as exp (expiration) and iat (issued at) alongside the user-specific ID.

Configuration and Public Key Management

CasaOS maintains JWT configuration in internal/conf/config.go, which includes a JwtSecret field defined as:

JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`

While this field supports symmetric HMAC signing as a fallback, the current implementation exclusively uses the asymmetric approach. The system loads the ECDSA public key from config.CommonInfo.RuntimePath using the external.GetPublicKey utility provided by CasaOS-Common, reading the PEM-encoded key at runtime.

Practical Implementation Examples

Making an Authenticated Request

Use the Bearer token scheme in the Authorization header:

curl -H "Authorization: Bearer $TOKEN" \
     http://localhost:80/v1/sys/hardware

Accessing User Context in Handlers

Retrieve the authenticated user ID from the injected header:

func GetSystemHardwareInfo(c echo.Context) error {
    userID := c.Request().Header.Get("user_id")
    log.Printf("hardware request from user %s", userID)
    
    // Implement business logic here
    return c.JSON(http.StatusOK, hardwareInfo)
}

Authenticating via Query Parameter

For requests where headers cannot be set:


http://localhost:80/v1/file?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...

Summary

  • Centralized Middleware: CasaOS uses echo_middleware.JWTWithConfig in route/v1.go and route/v2.go to protect all API endpoints under /v1 and /v2
  • ECDSA Verification: Token validation relies on asymmetric cryptography with public keys loaded from config.CommonInfo.RuntimePath via external.GetPublicKey
  • Flexible Token Sources: The system accepts tokens from the Authorization header (Bearer) or the token query parameter
  • Context Propagation: Validated user IDs propagate to handlers via the user_id request header, enabling stateless user identification
  • Hybrid Configuration: While internal/conf/config.go includes JwtSecret for symmetric signing, the active implementation uses ECDSA verification exclusively

Frequently Asked Questions

How does CasaOS validate JWT tokens on each request?

CasaOS validates JWT tokens through a custom ParseTokenFunc configured in the Echo middleware. This function calls jwt.Validate from the CasaOS-Common library, which verifies the token's ECDSA signature against the public key stored in the runtime directory. Validation occurs automatically for every request to /v1 and /v2 endpoints, rejecting unauthorized requests with a 401 status before they reach business logic.

What signing algorithm does CasaOS use for JWT verification?

CasaOS implements ECDSA (Elliptic Curve Digital Signature Algorithm) for JWT verification. The middleware uses a public key loaded via external.GetPublicKey(config.CommonInfo.RuntimePath) to verify signatures. While the configuration struct includes a JwtSecret field for symmetric HMAC signing, the current implementation exclusively uses the asymmetric ECDSA approach.

Where does CasaOS store the public key for token verification?

The public key resides in the runtime path specified by config.CommonInfo.RuntimePath. The external.GetPublicKey function in CasaOS-Common reads the PEM-encoded ECDSA public key from this location. This design allows the main CasaOS application to verify tokens without access to the private signing key, which remains secured in the authentication service or key management system.

Can CasaOS fall back to symmetric JWT signing?

Yes, the codebase includes infrastructure for symmetric signing through the JwtSecret configuration field in internal/conf/config.go. However, the current middleware implementation in route/v1.go and route/v2.go does not utilize this secret, instead relying exclusively on the ECDSA public key validation via jwt.Validate. The symmetric option remains available for future implementation or custom builds requiring HMAC-based verification.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →