Skip to content
dblike edited this page Jan 20, 2026 · 1 revision

OAuth API

Implement OAuth2 authorization with Lichess using the PKCE (Proof Key for Code Exchange) flow.

Namespace: LichessSharp.Api.Contracts Access: client.OAuth

Note: Lichess requires the PKCE flow for all OAuth applications. Client secrets are not used. Tokens are long-lived (approximately one year) and do not support refresh tokens.


Quick Start: Complete OAuth Flow

using LichessSharp;
using LichessSharp.Api.Contracts;

// Step 1: Create authorization request (generates PKCE values automatically)
using var client = new LichessClient();  // No token needed for auth URL generation

var (authUrl, codeVerifier) = client.OAuth.CreateAuthorizationRequest(
    clientId: "my-chess-app",
    redirectUri: "http://localhost:5000/callback",
    scopes: new[] { "preference:read", "challenge:write", "board:play" },
    state: Guid.NewGuid().ToString()  // For CSRF protection
);

// Store codeVerifier and state in session/database
// Redirect user to authUrl...

Console.WriteLine($"Redirect user to: {authUrl}");

// Step 2: After user authorizes, handle callback
// User is redirected back with ?code=xxx&state=yyy

string authorizationCode = "code_from_callback";  // Get from query string
string storedCodeVerifier = codeVerifier;         // Retrieve from session

var tokenRequest = new OAuthTokenRequest
{
    Code = authorizationCode,
    CodeVerifier = storedCodeVerifier,
    RedirectUri = "http://localhost:5000/callback",
    ClientId = "my-chess-app"
};

var token = await client.OAuth.GetTokenAsync(tokenRequest);

Console.WriteLine($"Access Token: {token.AccessToken}");
Console.WriteLine($"Expires In: {token.ExpiresIn} seconds");

// Step 3: Use the token for authenticated requests
using var authenticatedClient = new LichessClient(token.AccessToken);
var profile = await authenticatedClient.Account.GetProfileAsync();
Console.WriteLine($"Logged in as: {profile.Username}");

Available Scopes

Request only the scopes your application needs:

Scope Description
preference:read Read user preferences
preference:write Write user preferences
email:read Read user email address
challenge:read Read incoming challenges
challenge:write Create, accept, decline challenges
challenge:bulk Create bulk challenges
study:read Read private studies
study:write Create and edit studies
tournament:write Create and manage tournaments
racer:write Create and join puzzle races
puzzle:read Read puzzle activity
team:read Read team membership
team:write Join and leave teams
team:lead Manage teams you lead
follow:read Read followed players
follow:write Follow and unfollow players
msg:write Send private messages
board:play Play games with Board API
bot:play Play games with Bot API
engine:read Use external engine analysis
engine:write Register external engines
web:login Full website access (use sparingly)

Methods

CreateAuthorizationRequest

Generates PKCE values and creates the authorization URL for OAuth2 login. This is a convenience method that handles PKCE automatically.

(string AuthorizationUrl, string CodeVerifier) CreateAuthorizationRequest(
    string clientId,
    string redirectUri,
    IEnumerable<string>? scopes = null,
    string? state = null,
    string? username = null)

Parameters:

Name Type Description
clientId string Your application's unique identifier (arbitrary string)
redirectUri string URL to redirect back to after authorization
scopes IEnumerable? OAuth scopes to request
state string? Random string for CSRF protection
username string? Hint for which Lichess account to use

Returns: Tuple containing:

  • AuthorizationUrl - The URL to redirect the user to
  • CodeVerifier - The PKCE code verifier (store this securely!)

Example - Basic:

using var client = new LichessClient();

var (authUrl, codeVerifier) = client.OAuth.CreateAuthorizationRequest(
    clientId: "my-app",
    redirectUri: "http://localhost:5000/callback"
);

// Store codeVerifier in session
HttpContext.Session.SetString("code_verifier", codeVerifier);

// Redirect user
return Redirect(authUrl);

Example - With scopes and state:

var state = Guid.NewGuid().ToString();
HttpContext.Session.SetString("oauth_state", state);

var (authUrl, codeVerifier) = client.OAuth.CreateAuthorizationRequest(
    clientId: "my-chess-app",
    redirectUri: "https://myapp.com/auth/callback",
    scopes: new[] { "board:play", "challenge:write" },
    state: state
);

HttpContext.Session.SetString("code_verifier", codeVerifier);
return Redirect(authUrl);

Example - Pre-fill username:

// Useful when you know which account the user wants to authorize
var (authUrl, codeVerifier) = client.OAuth.CreateAuthorizationRequest(
    clientId: "my-app",
    redirectUri: "http://localhost:5000/callback",
    username: "DrNykterstein"  // Pre-fills the username field
);

GetTokenAsync

Exchange an authorization code for an access token.

Task<OAuthToken> GetTokenAsync(
    OAuthTokenRequest request,
    CancellationToken cancellationToken = default)

Parameters:

Name Type Description
request OAuthTokenRequest Token request with code and PKCE verifier
cancellationToken CancellationToken Cancellation token

OAuthTokenRequest Properties:

Property Type Description
Code string Authorization code from callback
CodeVerifier string PKCE code verifier from authorization request
RedirectUri string Must match the original redirect URI
ClientId string Must match the original client ID

Returns: OAuthToken with access token.

Example:

// In your callback endpoint
[HttpGet("/callback")]
public async Task<IActionResult> Callback(string code, string state)
{
    // Verify state matches
    var expectedState = HttpContext.Session.GetString("oauth_state");
    if (state != expectedState)
        return BadRequest("Invalid state");

    // Get stored code verifier
    var codeVerifier = HttpContext.Session.GetString("code_verifier");

    // Exchange code for token
    using var client = new LichessClient();
    var token = await client.OAuth.GetTokenAsync(new OAuthTokenRequest
    {
        Code = code,
        CodeVerifier = codeVerifier!,
        RedirectUri = "https://myapp.com/auth/callback",
        ClientId = "my-chess-app"
    });

    // Store token securely (e.g., in database)
    await SaveUserToken(token.AccessToken, token.ExpiresIn);

    return RedirectToAction("Dashboard");
}

RevokeTokenAsync

Revoke the current access token. The token used for Bearer authentication will be invalidated.

Task RevokeTokenAsync(CancellationToken cancellationToken = default)

Example:

// When user logs out
using var client = new LichessClient(userAccessToken);
await client.OAuth.RevokeTokenAsync();

// Token is now invalid - clear from storage
await DeleteUserToken(userId);

TestTokensAsync

Test multiple OAuth tokens to check their validity and scopes.

Task<IReadOnlyDictionary<string, OAuthTokenInfo?>> TestTokensAsync(
    IEnumerable<string> tokens,
    CancellationToken cancellationToken = default)

Parameters:

Name Type Description
tokens IEnumerable Tokens to test (up to 1000)
cancellationToken CancellationToken Cancellation token

Returns: Dictionary mapping each token to its info, or null if invalid.

Example:

using var client = new LichessClient();

var tokens = new[] { "lip_abc123", "lip_xyz789", "invalid_token" };
var results = await client.OAuth.TestTokensAsync(tokens);

foreach (var (token, info) in results)
{
    if (info == null)
    {
        Console.WriteLine($"Token {token[..10]}... is INVALID");
    }
    else
    {
        Console.WriteLine($"Token {token[..10]}... is valid");
        Console.WriteLine($"  User: {info.UserId}");
        Console.WriteLine($"  Scopes: {info.Scopes}");
        if (info.Expires.HasValue)
        {
            var expiry = DateTimeOffset.FromUnixTimeMilliseconds(info.Expires.Value);
            Console.WriteLine($"  Expires: {expiry}");
        }
    }
}

Example - Cleanup expired tokens:

var storedTokens = await GetAllStoredTokens();
var results = await client.OAuth.TestTokensAsync(storedTokens.Select(t => t.Token));

foreach (var (token, info) in results)
{
    if (info == null)
    {
        await DeleteStoredToken(token);
        Console.WriteLine($"Removed invalid token");
    }
}

Types Reference

OAuthToken

Access token response from GetTokenAsync().

Property Type Description
TokenType string Always "Bearer"
AccessToken string The access token for API requests
ExpiresIn int Seconds until expiration (typically ~1 year)

OAuthTokenInfo

Token information from TestTokensAsync().

Property Type Description
UserId string? Lichess user ID
Scopes string? Comma-separated scopes (empty string if none)
Expires long? Unix timestamp (ms) when token expires, null if never

OAuthTokenRequest

Request parameters for GetTokenAsync().

Property Type Description
Code string Authorization code from callback
CodeVerifier string PKCE code verifier
RedirectUri string Original redirect URI
ClientId string Original client ID

ASP.NET Core Integration Example

Here's a complete example for integrating Lichess OAuth in an ASP.NET Core application:

// Services/LichessAuthService.cs
public class LichessAuthService
{
    private readonly ILichessClient _client;
    private readonly IConfiguration _config;

    public LichessAuthService(ILichessClient client, IConfiguration config)
    {
        _client = client;
        _config = config;
    }

    public (string AuthUrl, string CodeVerifier, string State) CreateLoginRequest()
    {
        var state = Guid.NewGuid().ToString();
        var (authUrl, codeVerifier) = _client.OAuth.CreateAuthorizationRequest(
            clientId: _config["Lichess:ClientId"]!,
            redirectUri: _config["Lichess:RedirectUri"]!,
            scopes: new[] { "preference:read" },
            state: state
        );

        return (authUrl, codeVerifier, state);
    }

    public async Task<OAuthToken> ExchangeCodeAsync(string code, string codeVerifier)
    {
        return await _client.OAuth.GetTokenAsync(new OAuthTokenRequest
        {
            Code = code,
            CodeVerifier = codeVerifier,
            RedirectUri = _config["Lichess:RedirectUri"]!,
            ClientId = _config["Lichess:ClientId"]!
        });
    }
}

// Controllers/AuthController.cs
[Route("auth")]
public class AuthController : Controller
{
    private readonly LichessAuthService _authService;

    public AuthController(LichessAuthService authService)
    {
        _authService = authService;
    }

    [HttpGet("login")]
    public IActionResult Login()
    {
        var (authUrl, codeVerifier, state) = _authService.CreateLoginRequest();

        // Store in session for callback
        HttpContext.Session.SetString("oauth_state", state);
        HttpContext.Session.SetString("code_verifier", codeVerifier);

        return Redirect(authUrl);
    }

    [HttpGet("callback")]
    public async Task<IActionResult> Callback(string code, string state)
    {
        // Verify state
        var expectedState = HttpContext.Session.GetString("oauth_state");
        if (state != expectedState)
            return BadRequest("Invalid state parameter");

        // Exchange code
        var codeVerifier = HttpContext.Session.GetString("code_verifier")!;
        var token = await _authService.ExchangeCodeAsync(code, codeVerifier);

        // Get user profile
        using var authenticatedClient = new LichessClient(token.AccessToken);
        var profile = await authenticatedClient.Account.GetProfileAsync();

        // Clear session data
        HttpContext.Session.Remove("oauth_state");
        HttpContext.Session.Remove("code_verifier");

        // Create application session/cookie with user info
        // ... your authentication logic here ...

        return RedirectToAction("Index", "Home");
    }

    [HttpPost("logout")]
    public async Task<IActionResult> Logout()
    {
        var userToken = GetCurrentUserToken();
        if (userToken != null)
        {
            using var client = new LichessClient(userToken);
            await client.OAuth.RevokeTokenAsync();
        }

        // Clear application session
        // ... your logout logic here ...

        return RedirectToAction("Index", "Home");
    }
}

Security Best Practices

  1. Always use state parameter - Prevent CSRF attacks by generating a random state, storing it in the session, and verifying it in the callback.

  2. Store code verifier securely - The code verifier must be stored server-side (session, database) and never exposed to the client.

  3. Use HTTPS in production - OAuth redirect URIs should always use HTTPS in production.

  4. Request minimal scopes - Only request the scopes your application actually needs.

  5. Store tokens securely - Access tokens are long-lived credentials. Store them encrypted in your database.

  6. Handle token expiration - Lichess tokens last approximately one year. Implement token refresh logic by redirecting users through OAuth again when tokens expire.


See Also

Clone this wiki locally