GUIDEOAuth2

OAuth2

Built-in OAuth2 handlers for Discord's authorization flow. Token management, scope verification, and secure state handling out of the box.

OAuth2 setup

Configure OAuth2 in your Discord application, then wire the handler into your app.

typescript
import { createOAuth2Handler } from "@theminesastudios/mini-interaction";

const oauth = createOAuth2Handler({
  clientId: process.env.DISCORD_CLIENT_ID!,
  clientSecret: process.env.DISCORD_CLIENT_SECRET!,
  redirectUri: "https://your-site.com/api/auth/callback",
  scopes: ["identify", "guilds", "bot"],
});

// Generate authorization URL
const url = oauth.authorizeUrl({
  state: crypto.randomUUID(),
  guildId: "123456789",
  permissions: "8", // Administrator
});

// Handle the callback
const callback = oauth.callbackHandler(async (code, ctx) => {
  const tokens = await oauth.exchangeCode(code);
  // tokens.accessToken, tokens.refreshToken, tokens.expiresAt
  // Persist tokens in your database
  return new Response("Authenticated", { status: 200 });
});

export const GET = callback;

Token management

The framework handles token refresh, expiry detection, and scope validation.

typescript
import { TokenStore } from "@theminesastudios/mini-interaction";
import { createClient } from "@libsql/client";

const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
});

class TursoTokenStore extends TokenStore {
  async get(userId: string) {
    const row = await db.execute({
      sql: "SELECT * FROM tokens WHERE user_id = ?",
      args: [userId],
    });
    return row.rows[0] ?? null;
  }

  async set(userId: string, tokens: TokenSet) {
    await db.execute({
      sql: "INSERT OR REPLACE INTO tokens (user_id, access, refresh, expires) VALUES (?, ?, ?, ?)",
      args: [userId, tokens.accessToken, tokens.refreshToken, tokens.expiresAt.toISOString()],
    });
  }

  async delete(userId: string) {
    await db.execute({
      sql: "DELETE FROM tokens WHERE user_id = ?",
      args: [userId],
    });
  }
}

const oauth = createOAuth2Handler({
  // ... config
  store: new TursoTokenStore(),
});

Working with scopes

Validate and check scopes on tokens to gate functionality.

typescript
import { OAuthScopes } from "@theminesastudios/mini-interaction";

// Check if a token has the required scopes
const hasGuilds = oauth.hasScope(tokens, "guilds");
const hasBot = oauth.hasScope(tokens, "bot");

// Validate against required scopes
const valid = oauth.validateScopes(tokens, ["identify", "guilds"]);
// -> true if all required scopes are present

// Get user info with access token
const user = await oauth.getUser(tokens.accessToken);
// -> { id, username, discriminator, avatar, ... }

// List user's guilds
const guilds = await oauth.getUserGuilds(tokens.accessToken);
// -> Array of { id, name, icon, owner, permissions }

Token refresh

Automatic token refresh when the access token has expired. Works with the TokenStore for persistence.

typescript
// Automatic refresh via the store
const tokens = await oauth.getOrRefresh(userId);
if (!tokens) {
  // No valid token, redirect to OAuth again
  return Response.redirect(oauth.authorizeUrl({ state }));
}

// Manual refresh
const refreshed = await oauth.refreshToken(tokens.refreshToken);
// Store the new tokens
await tokenStore.set(userId, refreshed);

Configuration

Property
Type
clientIdrequired
string

Discord application client ID

clientSecretrequired
string

Discord application client secret (server-side only)

redirectUrirequired
string

OAuth2 redirect URI registered in Discord Developer Portal

scopes
string[]

Default OAuth2 scopes (default: ['identify'])

store
TokenStore

Persistent token store for refresh & lookup

OAuth2 endpoints

Method
Description
authorizeUrl()
Generate the Discord authorization URL with state and scopes
exchangeCode()
Exchange an OAuth2 code for access & refresh tokens
refreshToken()
Refresh an expired access token using the refresh token
getUser()
Fetch the authenticated user's profile from Discord
getUserGuilds()
List guilds the user is a member of
hasScope()
Check if a token set has a specific OAuth2 scope
validateScopes()
Verify a token set contains all required scopes