MainSDK
Search⌘K
Integrations

GitHub

Use GitHub sign-in for MainSDK auth - OAuth app setup, callback URLs, main.yaml, client secrets, local testing and swapping to another provider.

3 interfaces3 driversWritten guide
Updated 2026-10-28

GitHub is the default driver behind auth in the catalog. People sign in to your app with their GitHub account, and auth turns that into a short-lived, device-bound session your routes can trust. Pick GitHub when your users are developers who already have an account, such as a developer tool, an internal dashboard or a CLI with a web console. For a consumer app, a provider that holds its own user directory, such as Clerk or Auth0, is usually a better fit.

Interfaces it serves

Pick GitHub in main.yaml for any of these. Your calls stay the same, and you can swap later.

Before you start

You need a GitHub account, and you register your app with GitHub as an OAuth app, either under your personal account or under an organisation you administer.

  1. Register an OAuth app. In your GitHub developer settings (or your organisation's settings), create a new OAuth app. Give it a name your users will recognise on the consent screen.
  2. Set the homepage URL to your app's public address, for example https://shop.pinecart.dev.
  3. Set the authorization callback URL to the route in your app that receives GitHub's redirect after sign-in. The path MainSDK mounts for the callback is not defined yet. An OAuth app has one callback URL, so register a separate OAuth app for local development, stage and production.
  4. Copy the client ID and generate a client secret. GitHub shows the client secret once; copy it straight into your secrets manager. You can generate a new secret later and delete the old one to rotate it.
  5. Decide what you read about the user. Some people keep their GitHub email address private. If your app needs an email, request the scope that grants access to the user's email addresses, and handle users who have none verified.

Store the client ID and secret in your secrets manager, never in code or a committed env file.

Configure

main.yaml
auth:
  driver: github
  methods: [passkeys]
  # [to be decided] key names for the OAuth app; suggested secrets: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
  session: { ttl: 15m, refresh: 7d, bind: dbsc }

The auth spec does not yet define the keys a driver uses for its credentials. When it does, they take secret: references; a literal secret in main.yaml fails mainsdk check. Point each environment's secrets at the OAuth app registered for that environment.

Use it

Guard a route with auth.require(). Without a valid session it answers 401 with MAIN-AUTH-0001 and a fix.

app/api/me/route.ts
import { auth } from "mainsdk";

export async function GET(req: Request) {
  const session = await auth.require(req);
  return Response.json(session.user);
}

Ask for a role, a phishing-resistant method and a recent sign-in on sensitive routes:

app/api/admin/route.ts
import { auth } from "mainsdk";

export async function DELETE(req: Request) {
  await auth.require(req, { role: "admin", aal: "aal2", fresh: "5m" });
  return new Response(null, { status: 204 });
}

Where signing in is optional, call auth.session(req); it returns the session or null.

Local development and testing

  • No OAuth app yet: the local auth driver is for development only and needs no GitHub registration.
  • Real GitHub sign-in on your machine: register a separate OAuth app whose callback URL points at http://localhost on your dev port, and use its client ID and secret in your local secrets.
  • Tests: createTestApp() from @mainsdk/testing runs auth on its in-memory fake, so tests never call GitHub. chaos.on("auth", { outage: true }) simulates GitHub being unreachable; auth.require() fails closed.
app/api/me/route.test.ts
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp } from "@mainsdk/testing";
import { GET } from "./route";

it("auth.AC1 refuses a request with no session", async () => {
  createTestApp({ main: "./main.yaml" });
  const res = await GET(new Request("http://localhost/api/me"));
  assert.equal(res.status, 401);
});

What MainSDK adds on top

  • Short-lived, HttpOnly, Secure, SameSite sessions with rotating refresh tokens, never stored in localStorage.
  • Passkeys by default, phishing-resistant, with the browser's cross-device QR at no extra code.
  • Fails closed when GitHub cannot be reached.
  • WhatsApp codes and SSO for your customers' organisations, provided in your app where the driver lacks them; every recovery audited and never weaker than sign-in.

See Sign-in and sessions for the full contract.

Limits and notes

  • People sign in with their own GitHub accounts, so you don't hold a user directory at GitHub; profile and role data live in your own db.
  • How the GitHub driver combines with passkeys, WhatsApp and SSO is declared in its capability profile; mainsdk check names any method it lacks.
  • GitHub is a global service; residency in MainSDK's US, EU and India regions applies to the records you store about users, not to the GitHub account.
  • auth.raw() returns the provider client for features auth does not wrap. mainsdk check lists every use.

Swap to or from it

main.yaml
auth:
  driver: clerk   # was github

FAQ

Do I need a GitHub OAuth app to start building? No. Use the local driver in development and the fake in tests. Register an OAuth app when you want real GitHub sign-in.

Why one OAuth app per environment? Each OAuth app has a single callback URL, so local, stage and production each need their own registration and their own client secret.

Can I use a GitHub App instead of an OAuth app? This page covers OAuth apps only. Whether the driver supports GitHub Apps for sign-in is not specified.

Next

Open questions

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet