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.