Authorization

Chess Scanner uses the standard OAuth 2.0 Authorization Code flow with PKCE, and PKCE is required for every client. You run this flow once per user to get an access token, then call the API with it.

These endpoints live on the OAuth provider:

StepEndpoint
Authorize (browser redirect)GET https://chessscanner.com/api/auth/oauth2/authorize
Token exchange (server-side)POST https://chessscanner.com/api/auth/oauth2/token

1. Create a PKCE pair

Generate a random code_verifier and derive a code_challenge from it:

import { randomBytes, createHash } from "node:crypto";

const codeVerifier = randomBytes(48).toString("base64url");
const codeChallenge = createHash("sha256")
  .update(codeVerifier)
  .digest()
  .toString("base64url");

Keep the code_verifier around for step 3, for example in a short-lived, httpOnly cookie. Also generate a random state value to protect against CSRF.

2. Redirect the user to authorize

Send the user's browser to the authorize endpoint:

https://chessscanner.com/api/auth/oauth2/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://your-app.com/oauth/callback
  &scope=user.read databases.read games.read offline_access
  &state=RANDOM_STATE
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256

The user signs in if needed, approves the consent screen, and comes back to your redirect_uri with a code and the state:

https://your-app.com/oauth/callback?code=AUTH_CODE&state=RANDOM_STATE

3. Exchange the code for a token

On your server, check that state matches what you sent, then POST to the token endpoint as application/x-www-form-urlencoded:

curl -X POST https://chessscanner.com/api/auth/oauth2/token \
  -d grant_type=authorization_code \
  -d code=AUTH_CODE \
  -d redirect_uri=https://your-app.com/oauth/callback \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code_verifier=YOUR_CODE_VERIFIER

Response:

{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "user.read databases.read games.read"
}

4. Use the token

Call the API with the access token:

curl https://api.chessscanner.com/api/v1/me \
  -H "Authorization: Bearer ACCESS_TOKEN"

See the API Reference for all endpoints.

Refresh tokens (long-lived access)

Ask for the offline_access scope (as in the example above) and you'll get a refresh token along with the access token:

{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "…",
  "scope": "user.read databases.read games.read offline_access"
}

Store the refresh token securely. When the access token expires, trade the refresh token for a new one without any user interaction:

curl -X POST https://chessscanner.com/api/auth/oauth2/token \
  -d grant_type=refresh_token \
  -d refresh_token=YOUR_REFRESH_TOKEN \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET

That's how a backend can keep pulling a user's games over time. If the response includes a new refresh_token, replace the one you stored.

Good to know

  • The token endpoint uses client_secret_post, so send client_id and client_secret in the request body (as shown above).
  • The scopes you request have to be a subset of what your app was granted, otherwise you get invalid_scope.
  • Tokens are opaque, not JWTs. Validate them by calling the API rather than decoding them.

Was this page helpful?