API Authentication and Authorization#
Sure's programmatic API (/api/v1) supports two authentication methods: OAuth 2.0 Bearer tokens and API keys. Both are handled by Api::V1::BaseController, which skips session-based auth and CSRF, then runs authenticate_request!, check_api_key_rate_limit, and log_api_access as before_actions on every request.
Authentication Flow#
authenticate_request! tries each method in order; the first success sets @current_user and @authentication_method, then calls setup_current_context_for_api to attach a fresh, unsaved Session to Current.session — ensuring API requests never inherit web session state or impersonation context .
Request
└─ authenticate_oauth (Authorization: Bearer <token>)
└─ on failure → authenticate_api_key (X-Api-Key: <key>)
└─ on failure → 401 Unauthorized
OAuth 2.0 (Doorkeeper)#
- Header:
Authorization: Bearer <token> authenticate_oauthlooks up the token viaDoorkeeper::AccessToken.by_token, then verifiesaccessible?and that the token carries at leastreadorread_writescope.- Doorkeeper is configured with 1-year token expiration , PKCE enforcement for non-confidential clients , refresh tokens , and SHA256 secret hashing in production .
- OAuth discovery endpoints are exposed at
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server; dynamic client registration is available atPOST /register.
API Keys#
- Header:
X-Api-Key: <key> authenticate_api_keycallsApiKey.find_by_value, which looks up the key using deterministic AES encryption — enabling indexed lookups without storing plaintext.- Keys track
last_used_at(updated on each request viaupdate_last_used!) and support optional expiration (expires_at) and revocation (revoke!) . - Rate limiting is initialized here:
@rate_limiter = ApiRateLimiter.limit(@api_key).
Scope Model#
Both authentication methods use the same two-scope model, enforced by authorize_scope!:
| Scope | Access |
|---|---|
read | Read-only endpoints |
read_write | Read + write; implicitly includes read access |
read is the Doorkeeper default scope . Write endpoints call authorize_scope!(:write), which requires read_write; attempting a write with only read returns 403 Forbidden with "insufficient_scope".
current_scopes abstracts over both auth methods — returning doorkeeper_token.scopes for OAuth or @api_key.scopes for API keys — so authorize_scope! is auth-method-agnostic.
Rate Limiting (API Keys Only)#
Rate limiting applies only to API key authentication; OAuth tokens are not rate-limited .
check_api_key_rate_limit runs after authentication. It uses a Redis-backed sliding hourly window :
| Tier | Requests / Hour |
|---|---|
standard (default) | 100 |
premium | 1,000 |
enterprise | 10,000 |
Rate limit status is surfaced in response headers on every API key request :
X-RateLimit-Limit— the tier ceilingX-RateLimit-Remaining— requests left in the current windowX-RateLimit-Reset— seconds until the window resetsRetry-After— also set on429 Too Many Requestsresponses
Self-hosted deployments use NoopApiRateLimiter, which always returns false for rate_limit_exceeded? and Float::INFINITY for the limit , effectively disabling rate limiting.
Key Files#
| File | Role |
|---|---|
app/controllers/api/v1/base_controller.rb | Auth dispatch, scope enforcement, rate limit hooks, error handlers |
app/models/api_key.rb | Key encryption, scopes, expiry, revocation |
app/services/api_rate_limiter.rb | Redis-backed rate limiting with tier support |
config/initializers/doorkeeper.rb | OAuth server config: PKCE, token TTL, SHA256 hashing, scopes |