Single Sign-On (SSO)

Custom SSO Adapter

Build a custom OAuth2 Single Sign-On adapter for any provider with the SSOApiPlugin interface.

VitNode includes built-in SSO adapters for Google, Discord, and Facebook. You can connect any other OAuth2 provider (e.g. GitHub, Apple, Slack) by implementing the SSOApiPlugin interface. State cookie generation, user session linking, and login buttons are handled automatically.

Quick start: GitHub SSO Adapter

Implement the 5 interface methods and register the adapter in vitnode.api.config.ts:

1. Build the Adapter

apps/api/src/utils/sso/github.ts
import type { SSOApiPlugin } from "@vitnode/core/api/models/sso"
import { getRedirectUri } from "@vitnode/core/api/models/sso"

export const GitHubSSOApiPlugin = ({
  clientId,
  clientSecret,
}: {
  clientId: string
  clientSecret: string
}): SSOApiPlugin => {
  const id = "github"
  const redirectUri = getRedirectUri(id)

  return {
    id,
    name: "GitHub",
    // 1. Build Authorization URL
    getUrl: ({ state }) =>
      `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=read:user,user:email&state=${state}`,

    // 2. Exchange Authorization Code for Token
    fetchToken: async (code) => {
      const response = await fetch("https://github.com/login/oauth/access_token", {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          client_id: clientId,
          client_secret: clientSecret,
          code,
          redirect_uri: redirectUri,
        }),
      })

      return await response.json()
    },

    // 3. Retrieve User Profile & Email
    fetchUser: async ({ access_token, token_type }) => {
      const headers = { Authorization: `${token_type} ${access_token}` }

      // Fetch Profile
      const userRes = await fetch("https://api.github.com/user", { headers })
      const user = await userRes.json()

      // Fetch Primary Email
      const emailRes = await fetch("https://api.github.com/user/emails", { headers })
      const emails: Array<{ email: string; primary: boolean; verified: boolean }> =
        await emailRes.json()
      const primaryEmail = emails.find((e) => e.primary && e.verified)?.email ?? user.email

      return {
        id: String(user.id),
        email: primaryEmail,
        username: user.login,
        avatarUrl: user.avatar_url,
      }
    },
  }
}

2. Register in API Configuration

apps/api/src/vitnode.api.config.ts
import { buildApiConfig } from "@vitnode/core/vitnode.config"
import { GitHubSSOApiPlugin } from "./utils/sso/github"

export const vitNodeApiConfig = buildApiConfig({
  authorization: {
    ssoAdapters: [
      GitHubSSOApiPlugin({
        clientId: process.env.GITHUB_CLIENT_ID!,
        clientSecret: process.env.GITHUB_CLIENT_SECRET!,
      }),
    ],
  },
})

A GitHub login button automatically renders on /login and /register, and /login/sso/github routes incoming authentication requests.


SSOApiPlugin Interface Reference

Prop

Type

Built-in SSO Adapters