Skip to the content.

For a guide with screenshots, see: OAuth2 How To


OAuth2 Authentication

OAuth2 allows third-party applications to authenticate users and access the OGS API on their behalf.

Endpoints

Endpoint URL
Authorization https://online-go.com/oauth2/authorize/
Token https://online-go.com/oauth2/token/
Revoke Token https://online-go.com/oauth2/revoke_token/
Introspect https://online-go.com/oauth2/introspect/
User Info https://online-go.com/oauth2/userinfo/

Available Scopes

Scope Description
read Read access to user data
write Write access to user data
groups Access to user’s groups

Application Registration

Register your application to obtain a client_id and client_secret.

Limits: Each user can register up to 5 OAuth2 applications.

Client Types

Via Web Interface

Register at https://online-go.com/oauth2/applications/.

Via API

curl -X POST https://online-go.com/api/v1/me/api_clients/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-user-token>" \
  -d '{
    "name": "My Application",
    "client_type": "confidential",
    "authorization_grant_type": "authorization-code",
    "redirect_uris": "https://yoursite.com/callback"
  }'

Response:

{
    "id": 123,
    "client_id": "abc123def456",
    "client_secret": "supersecretkey789",
    "name": "My Application",
    "client_type": "confidential",
    "authorization_grant_type": "authorization-code",
    "redirect_uris": "https://yoursite.com/callback"
}

Important: Save the client_secret immediately - it cannot be retrieved later.

Redirect URI Schemes

Supported URI schemes for redirect URIs:


Authorization Code Flow

This is the recommended flow for third-party applications.

Step 1: Redirect User to Authorization

https://online-go.com/oauth2/authorize/?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https://yoursite.com/callback&scope=read+write&state=RANDOM_STATE

Parameters:

Parameter Required Description
client_id Yes Your application’s client ID
response_type Yes Must be code
redirect_uri Yes Must exactly match a registered redirect URI
scope No Space-separated scopes: read, write, groups
state Yes* Random value for CSRF protection (*technically optional but always use it)

Step 2: Handle the Callback

After authorization, OGS redirects to your callback URL:

Success:

https://yoursite.com/callback?code=AUTH_CODE&state=YOUR_STATE

Error:

https://yoursite.com/callback?error=access_denied&error_description=User+denied+access&state=YOUR_STATE

Always validate the state parameter matches what you sent.

Step 3: Exchange Code for Tokens

Use HTTP Basic Authentication (recommended):

curl -X POST https://online-go.com/oauth2/token/ \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://yoursite.com/callback"

Or include credentials in the body:

curl -X POST https://online-go.com/oauth2/token/ \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "redirect_uri=https://yoursite.com/callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Response:

{
    "access_token": "your-access-token",
    "token_type": "Bearer",
    "expires_in": 2592000,
    "refresh_token": "your-refresh-token",
    "scope": "read write"
}

Token Expiration

Token Lifetime
Access Token 30 days (2592000 seconds)
Refresh Token 30 days (2592000 seconds)

Note: Both tokens expire after 30 days. After the refresh token expires, users must re-authenticate.

Step 4: Use Access Token

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  https://online-go.com/api/v1/me/

Step 5: Refresh Tokens

Before the access token expires, obtain a new one:

curl -X POST https://online-go.com/oauth2/token/ \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN"

Revoke Tokens

To invalidate a token (e.g., on logout):

curl -X POST https://online-go.com/oauth2/revoke_token/ \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "token=TOKEN_TO_REVOKE"

JavaScript Example

// Step 1: Initiate login
function loginWithOGS() {
    const clientId = "your-client-id";
    const redirectUri = "https://yoursite.com/callback";
    const state = crypto.randomUUID();

    sessionStorage.setItem("oauth_state", state);

    const params = new URLSearchParams({
        client_id: clientId,
        redirect_uri: redirectUri,
        response_type: "code",
        scope: "read write",
        state: state,
    });

    window.location.href = `https://online-go.com/oauth2/authorize/?${params}`;
}

// Step 2: Handle callback
async function handleCallback() {
    const params = new URLSearchParams(window.location.search);

    // Check for errors
    const error = params.get("error");
    if (error) {
        const description = params.get("error_description") || "Unknown error";
        throw new Error(`OAuth error: ${error} - ${description}`);
    }

    // Validate state
    const state = params.get("state");
    if (state !== sessionStorage.getItem("oauth_state")) {
        throw new Error("State mismatch - possible CSRF attack");
    }

    // Exchange code for tokens (send to your backend)
    const code = params.get("code");
    const response = await fetch("/api/auth/ogs/callback", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ code }),
    });

    if (!response.ok) {
        throw new Error("Token exchange failed");
    }

    const data = await response.json();
    // Handle authenticated user...
}

PKCE for Public Clients

For mobile apps and SPAs that cannot securely store a client secret, use PKCE (Proof Key for Code Exchange).

Complete PKCE Flow

// Generate PKCE parameters
function generateCodeVerifier() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return btoa(String.fromCharCode(...array))
        .replace(/\+/g, "-")
        .replace(/\//g, "_")
        .replace(/=/g, "");
}

async function generateCodeChallenge(verifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(verifier);
    const hash = await crypto.subtle.digest("SHA-256", data);
    return btoa(String.fromCharCode(...new Uint8Array(hash)))
        .replace(/\+/g, "-")
        .replace(/\//g, "_")
        .replace(/=/g, "");
}

// Step 1: Authorization request with PKCE
async function loginWithPKCE() {
    const clientId = "your-client-id";
    const redirectUri = "https://yoursite.com/callback";
    const state = crypto.randomUUID();
    const codeVerifier = generateCodeVerifier();
    const codeChallenge = await generateCodeChallenge(codeVerifier);

    // Store for later
    sessionStorage.setItem("oauth_state", state);
    sessionStorage.setItem("code_verifier", codeVerifier);

    const params = new URLSearchParams({
        client_id: clientId,
        redirect_uri: redirectUri,
        response_type: "code",
        scope: "read write",
        state: state,
        code_challenge: codeChallenge,
        code_challenge_method: "S256",
    });

    window.location.href = `https://online-go.com/oauth2/authorize/?${params}`;
}

// Step 2: Token exchange with PKCE (no client_secret needed)
async function exchangeCodeWithPKCE(code) {
    const codeVerifier = sessionStorage.getItem("code_verifier");

    const response = await fetch("https://online-go.com/oauth2/token/", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({
            grant_type: "authorization_code",
            code: code,
            redirect_uri: "https://yoursite.com/callback",
            client_id: "your-client-id",
            code_verifier: codeVerifier,
        }),
    });

    return response.json();
}

Error Responses

Common OAuth2 errors returned in callbacks or token requests:

Error Description
invalid_request Missing or invalid parameter
invalid_client Client authentication failed
invalid_grant Authorization code is invalid or expired
unauthorized_client Client not authorized for this grant type
access_denied User denied the authorization request
invalid_scope Requested scope is invalid or unknown

Security Best Practices