Built-in OAuth2 handlers for Discord's authorization flow. Token management, scope verification, and secure state handling out of the box.
Configure OAuth2 in your Discord application, then wire the handler into your app.
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;The framework handles token refresh, expiry detection, and scope validation.
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(),
});Validate and check scopes on tokens to gate functionality.
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 }Automatic token refresh when the access token has expired. Works with the TokenStore for persistence.
// 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);clientIdrequiredstringDiscord application client ID
clientSecretrequiredstringDiscord application client secret (server-side only)
redirectUrirequiredstringOAuth2 redirect URI registered in Discord Developer Portal
scopesstring[]Default OAuth2 scopes (default: ['identify'])
storeTokenStorePersistent token store for refresh & lookup
authorizeUrl()exchangeCode()refreshToken()getUser()getUserGuilds()hasScope()validateScopes()