Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.flexslot.gg/llms.txt

Use this file to discover all available pages before exploring further.

OAuth is only as secure as the weakest link in your implementation. This page enumerates the practices Flexslot requires and the ones we strongly recommend. Most of these come straight from RFC 9700 (BCP 240, the OAuth 2.0 Security Best Current Practice). Treat them as MUSTs.

The non-negotiables

PKCE S256 always

Every flow, every client type. plain is forbidden.

Exact redirect URI match

No wildcards. No prefix matching. Byte-for-byte.

state on every request

Random, single-use, bound to the user’s session.

Validate iss on callback

RFC 9207. Reject if missing or wrong.

Refresh token rotation

Store the new token, drop the old.

HTTPS everywhere

No exceptions for production.

PKCE (Proof Key for Code Exchange)

What it does: binds the authorization code to your client, so an intercepted code is useless to anyone else. Required at Flexslot for:
  • Every client type — public and confidential.
  • Every authorization code request.
Required method: S256. plain is rejected.
code_challenge = BASE64URL(SHA256(code_verifier))
1

Generate a 32-byte random code_verifier

Base64url-encoded, no padding. 43 characters.
2

Hash to produce code_challenge

SHA256(verifier), base64url-encoded, no padding.
3

Send the challenge on /authorize

With code_challenge_method=S256.
4

Send the verifier on /token

Flexslot recomputes the hash and rejects if it doesn’t match.
See Authorization Code Flow for working code in Node, Python, and bash.
PKCE downgrade defense: if you registered with code_challenge_method=S256, sending plain later is rejected. Flexslot also rejects token requests that include code_verifier if the original authorization request had no code_challenge.

redirect_uri matching

Flexslot performs exact string match on the redirect_uri parameter against the URIs registered for your client. There is no wildcard support, no path-prefix tolerance, and no port-flexibility (except 127.0.0.1 and [::1] for native apps’ loopback redirects).
Registered URIRequest URIMatch?
https://app.example.com/oauth/callbackhttps://app.example.com/oauth/callbackYes
https://app.example.com/oauth/callbackhttps://app.example.com/oauth/callback/No (trailing slash differs)
https://app.example.com/oauth/callbackhttps://app.example.com/oauth/callback?foo=1No (query string differs)
https://app.example.com/oauth/callbackhttps://APP.example.com/oauth/callbackNo (case differs)
https://app.example.com/oauth/callbackhttp://app.example.com/oauth/callbackNo (scheme differs)
  • One URI per environment. Register https://dev.app.example.com/oauth/callback AND https://app.example.com/oauth/callback. Don’t try to use one with a wildcard.
  • Avoid query strings. Keep state in a session, not in the URI.
  • Use HTTPS. http:// is allowed only for localhost, 127.0.0.1, and [::1] (for local development and native apps).

state parameter

The state parameter is a CSRF token: it proves that the callback corresponds to a flow your application initiated.

Requirements

  • Cryptographically random — at least 128 bits of entropy.
  • Bound to the user agent’s session (a cookie, server-side session storage).
  • Single-use — invalidate after the callback consumes it.
  • Validated on callback — strict equality compared to the stored value.
Don’t try to encode application data into state (return URL, intent, anything). Use a random opaque token, and store any data you need server-side keyed by that token. This keeps the CSRF surface minimal and avoids accidental information disclosure.

What happens if you skip it

PKCE protects against code interception, but state is the universally-compatible defense against the simpler attack of “trick the user’s browser into hitting your callback with an attacker-controlled code”. Without state, an attacker who can convince the user to visit a URL can bind the user’s session to the attacker’s Flexslot account.

Issuer identification (RFC 9207)

Every callback from Flexslot includes an iss query parameter:
GET /oauth/callback?code=…&state=…&iss=https://api.flexslot.gg
You must verify iss == "https://api.flexslot.gg". If it’s missing or different, abort the flow — that’s a mix-up attack signal. The mix-up attack: a malicious or compromised authorization server tricks your client into sending a code intended for AS A to AS B. The iss parameter cryptographically labels which AS issued the code so your client can tell.

Refresh token rotation

Every successful grant_type=refresh_token exchange:
  1. Returns a new refresh token in the response.
  2. Invalidates the refresh token you just used.
If you replay an old refresh token, Flexslot treats that as a theft signal and revokes the entire grant. The user has to consent again.

What this means for your code

1

Call /token with the current refresh_token

Get back access_token + new refresh_token.
2

Store the new refresh_token immediately

Atomically replace the old one in your database. The old one is dead.
3

If the response is invalid_grant

Treat the user as logged out. Their next action will need a fresh authorization flow.
Race conditions are real. If two parallel requests both refresh, one of them will lose. Serialize refresh calls per-user (a mutex, a row-level lock, or an idempotency key) and have the loser re-read the now-stored token instead of erroring.

Token storage

WhereAcceptable for
Encrypted database columnServer-side apps — recommended
Encrypted RedisServer-side apps — recommended
HTTP-only cookieRefresh token for web apps using a BFF pattern
In-memory variableSPA access tokens (no JavaScript-accessible storage)
localStorage / sessionStorageNever for tokens — XSS reads it all
URL query stringNever. RFC 9700 forbids tokens in URIs.
Server logsNever. Mask tokens before logging.

TLS

  • All Flexslot endpoints require TLS 1.2 or higher.
  • Loopback (http://127.0.0.1) is allowed for development and native apps.
  • We send Cache-Control: no-store on every token response — your client should not cache them.

CSRF on the token endpoint

The token endpoint is POST-only and requires either:
  • HTTP Basic auth with the client credentials (for confidential clients), or
  • client_id in the form body plus the PKCE code_verifier (for public clients).
Browsers can’t forge either of these from a third-party origin without an XSS or stolen credentials, so the token endpoint is not a CSRF target in the same way /authorize is. CSRF protection on /authorize is the job of the state parameter.

Logging out / revocation

When a user logs out of your app:
  1. Call /revoke with their refresh token. This invalidates both the refresh token and any active access tokens for the grant.
  2. Clear local state — remove tokens from your store.
curl -X POST https://api.flexslot.gg/api/public/v1/oauth/revoke \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "token=$REFRESH_TOKEN" \
  -d "token_type_hint=refresh_token"
A successful revocation returns 200 with an empty body (RFC 7009).

When tokens leak

If a token is logged, exposed in an error page, or shows up in a screenshot — treat it as compromised and revoke it immediately, then rotate any related secrets. The 1-hour access token expiry limits blast radius, but a leaked refresh token is good for 30 days unless revoked.

Sender-constrained tokens (DPoP / mTLS)

Bearer tokens are exactly that — whoever holds one can use it. For high-value integrations, consider sender-constraining with DPoP (RFC 9449) so that a stolen token can’t be replayed by an attacker who doesn’t have your client’s private key. See DPoP.

Threats this defends against

ThreatDefenseWhere
Authorization code interceptionPKCEauthorization-code-flow
CSRF on callbackstateThis page
Mix-up attackiss validationThis page
Open redirector via wildcard redirect_uriExact URI matchThis page
Refresh token theftRotation + revoke on replayThis page
XSS exfiltrating tokensIn-memory storage + httpOnly cookiesThis page
Token replay across servicesDPoP / mTLS sender constraintDPoP

Further reading