BeatKhana Development

OAuth 2.0

Application registration, authorization code, PKCE, scopes, refresh rotation and revocation with examples.

Register an application

Open Developers → Applications, choose an alphanumeric name, optional CDN image, requested scopes and exact redirect URLs. Save a confidential client secret immediately: only its hash is stored and plaintext is shown once. Rotate it if lost or exposed.

HTTP redirects are allowed only for localhost, 127.0.0.1, ::1 or hosts ending .local; every other redirect must use HTTPS. Matching is exact. Native/public clients cannot keep a secret and must require S256 PKCE.

Authorization request

GET https://api.beatkhana.com/api/oauth/authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&
  scope=rest%3Auser%3Aread%20ws%3Aread&
  state=RANDOM_CSRF_VALUE&
  code_challenge=BASE64URL_SHA256_VERIFIER&
  code_challenge_method=S256

Generate unpredictable state per attempt, store it in the user's server session, and compare it in constant time after redirect. A successful approval returns code and state. Denial returns an OAuth error. Never exchange an unverified callback.

Exchange the code

Authorization codes are single-use and expire after ten minutes. Confidential clients should authenticate with HTTP Basic; public clients provide client_id and code_verifier without a secret.

curl -u 'CLIENT_ID:CLIENT_SECRET' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code' \
  -d 'code=RETURNED_CODE' \
  -d 'redirect_uri=https://example.com/callback' \
  https://api.beatkhana.com/api/oauth/token
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 5184000,
  "refresh_token": "...",
  "scope": "rest:user:read ws:read"
}

Use Authorization: Bearer ACCESS_TOKEN for API calls.

Refresh rotation

curl -u 'CLIENT_ID:CLIENT_SECRET' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=refresh_token' \
  -d 'refresh_token=CURRENT_REFRESH_TOKEN' \
  https://api.beatkhana.com/api/oauth/token

Every successful refresh invalidates the submitted refresh token and returns a replacement. Store the replacement atomically. Reuse of rotated tokens must be treated as a compromised session.

Revoke

POST form-encoded token=... with client authentication to https://api.beatkhana.com/api/oauth/revoke. Deleting an OAuth application deletes its pending requests, codes and refresh tokens. Rotating the application secret does not justify putting it in a browser or desktop binary.

Scopes

ScopeCapability
rest:user:readRead /users/@me.
rest:user:writeUpdate self and implies user read.
rest:readRead REST resources and implies user read.
rest:writeRequest REST mutations and implies REST/user read; user roles still apply.
ws:readRead-only socket subscriptions/data.
ws:writeSocket commands and implies ws:read; roles still apply.
tournamentassistantSpecialized TA management/client access.
tournamentassistant:gameSpecialized in-game TA access.

Server example

app.get('/auth/beatkhana/callback', async (req, res) => {
  verifyState(req.query.state, req.session.oauthState);
  const body = new URLSearchParams({
    grant_type: 'authorization_code',
    code: String(req.query.code),
    redirect_uri: 'https://example.com/auth/beatkhana/callback',
  });
  const token = await fetch('https://api.beatkhana.com/api/oauth/token', {
    method: 'POST',
    headers: {
      authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
      'content-type': 'application/x-www-form-urlencoded',
    },
    body,
  });
  if (!token.ok) return res.status(502).send(await token.text());
  await saveTokensAtomically(req.session.userId, await token.json());
  res.redirect('/');
});

On this page