Skip to content

OAuth2

Kyrin provides built-in OAuth2 client support for social login providers. You can authenticate users via Google, GitHub, Facebook, and Discord.

Supported Providers

ProviderOAuthProvider value
Google"google"
GitHub"github"
Facebook"facebook"
Discord"discord"

Configuration

Before using OAuth2, you need to register your application with each provider to get your clientId and clientSecret.

Using the Auth Class

typescript
import { createAuth } from "kyrin";

const auth = createAuth({ jwtSecret: process.env.JWT_SECRET! });

auth.oauth.configure({
  provider: "google",
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  redirectUri: "https://your-app.com/auth/google/callback",
  scope: ["openid", "email", "profile"],
});

OAuthConfig

typescript
interface OAuthConfig {
  provider: OAuthProvider;     // "google" | "github" | "facebook" | "discord"
  clientId: string;            // OAuth app client ID
  clientSecret: string;        // OAuth app client secret
  redirectUri: string;         // Callback URL after authentication
  scope?: string[];            // Requested permissions (provider-dependent)
}

Default Scopes by Provider

ProviderDefault Scopes
Google["openid", "email", "profile"]
GitHub["read:user", "user:email"]
Facebook["email", "public_profile"]
Discord["identify", "email"]

Authorization Flow

The OAuth2 flow has three steps:

1. Redirect to Provider

Generate the authorization URL and redirect the user.

typescript
import { createAuth } from "kyrin";

const auth = createAuth({ jwtSecret: process.env.JWT_SECRET! });

// Configure the provider
auth.oauth.configure({
  provider: "github",
  clientId: process.env.GITHUB_CLIENT_ID!,
  clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  redirectUri: "https://your-app.com/auth/github/callback",
});

// Generate a random state value for CSRF protection
const state = auth.oauth.createState();

// Get the authorization URL
const url = auth.oauth.getAuthUrl("github", state);

// Redirect the user
// Response.redirect(url);

2. Handle the Callback

After the user authenticates, the provider redirects back to your redirectUri with an authorization code. Exchange this code for tokens.

typescript
// In your callback route (e.g., /auth/github/callback)
app.get("/auth/github/callback", async (c) => {
  const code = c.query("code");
  const state = c.query("state");

  if (!code || !state) {
    return c.json({ error: "Missing code or state" }, 400);
  }

  // Exchange the authorization code for tokens
  const tokens = await auth.oauth.exchangeToken("github", code);
  if (!tokens) {
    return c.json({ error: "Failed to exchange token" }, 400);
  }

  // Get the user's profile information
  const user = await auth.oauth.getUser("github", tokens.accessToken);
  if (!user) {
    return c.json({ error: "Failed to get user info" }, 400);
  }

  // Create a session or JWT for your app
  const session = auth.createSession(user.id, {
    provider: "github",
    email: user.email,
    name: user.name,
  });

  return c.json({
    sessionId: session.id,
    user,
  });
});

3. Token Refresh

Access tokens expire. Use the refresh token to get a new one.

typescript
const newTokens = await auth.oauth.refresh("github", refreshToken);
if (newTokens) {
  console.log("New access token:", newTokens.accessToken);
}

Standalone OAuth2 Functions

For more control, use the standalone functions directly.

typescript
import {
  setOAuthConfig,
  getOAuthConfig,
  getAuthorizationUrl,
  exchangeCodeForToken,
  getUserInfo,
  refreshOAuthToken,
  generateState,
} from "kyrin";

// Configure
setOAuthConfig({
  provider: "google",
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  redirectUri: "https://your-app.com/auth/google/callback",
});

// Get config
const config = getOAuthConfig("google");

// Generate state and get URL
const state = generateState();
const url = getAuthorizationUrl("google", state);

// Exchange code for tokens
const tokens = await exchangeCodeForToken("google", code);

// Get user info
const user = await getUserInfo("google", tokens.accessToken);

// Refresh token
const refreshed = await refreshOAuthToken("google", tokens.refreshToken!);

Types

OAuthTokens

typescript
interface OAuthTokens {
  accessToken: string;
  refreshToken?: string;      // Some providers may not return a refresh token
  tokenType: string;          // e.g., "bearer"
  expiresIn: number;          // Seconds until the access token expires
}

OAuthUserInfo

typescript
interface OAuthUserInfo {
  id: string;                 // Provider-specific user ID
  email?: string;
  name?: string;
  picture?: string;
  provider: OAuthProvider;
  avatar?: string;            // Alias for picture
}

Complete Example

typescript
import { createAuth } from "kyrin";
import { createApp } from "kyrin";

const auth = createAuth({ jwtSecret: process.env.JWT_SECRET! });
const app = createApp();

// --- Configure providers ---
auth.oauth.configure({
  provider: "google",
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  redirectUri: "https://your-app.com/auth/google/callback",
});

auth.oauth.configure({
  provider: "github",
  clientId: process.env.GITHUB_CLIENT_ID!,
  clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  redirectUri: "https://your-app.com/auth/github/callback",
});

// --- Login page ---
app.get("/login", async (c) => {
  const googleState = auth.oauth.createState();
  const githubState = auth.oauth.createState();

  return c.json({
    google: auth.oauth.getAuthUrl("google", googleState),
    github: auth.oauth.getAuthUrl("github", githubState),
  });
});

// --- OAuth callback handler ---
async function handleOAuthCallback(provider: "google" | "github", c: any) {
  const { code, state } = c.query();
  if (!code) return c.json({ error: "Missing authorization code" }, 400);

  const tokens = await auth.oauth.exchangeToken(provider, code);
  if (!tokens) return c.json({ error: "Token exchange failed" }, 400);

  const user = await auth.oauth.getUser(provider, tokens.accessToken);
  if (!user) return c.json({ error: "Failed to get user info" }, 400);

  const session = auth.createSession(user.id, {
    provider,
    email: user.email,
    name: user.name,
  });

  const jwt = await auth.signToken(user.id, {
    role: "user",
  });

  return c.json({ sessionId: session.id, ...jwt, user });
}

app.get("/auth/google/callback", (c) => handleOAuthCallback("google", c));
app.get("/auth/github/callback", (c) => handleOAuthCallback("github", c));

// --- Protected route ---
app.get("/me", async (c) => {
  const sessionId = c.req.headers.get("X-Session-Id");
  if (!sessionId) return c.json({ error: "Not authenticated" }, 401);

  const session = auth.getSession(sessionId);
  if (!session) return c.json({ error: "Session expired" }, 401);

  return c.json({
    userId: session.userId,
    profile: session.data,
  });
});

Next Steps

Released under the MIT License.