Sign in with GitHub using OAuth

Add "Sign in with GitHub" to an app: log users in, protect a route, and let them log out. The same steps work for any OAuth provider.

1. Register a GitHub OAuth app

In GitHub, go to Settings → Developer settings → OAuth Apps → New OAuth App. Set the Authorization callback URL to http://localhost:3000/auth/callback/github (use your real host in production). GitHub gives you a Client ID and a Client Secret.

2. Configure the server

There is no database to set up, so the credentials are the only configuration:

GITHUB_ID=your-client-id
GITHUB_SECRET=your-client-secret
SECRETS=a-long-random-string  # signs the session cookie
import server from '@server/next';

export default server({ auth: 'cookie:github' })
  .get('/', (ctx) =>
    ctx.user
      ? `Hi ${ctx.user.name}`
      : '<a href="/auth/login/github">Sign in with GitHub</a>',
  );

In development that is all you need: the profile is signed into the cookie, so there is nothing to store and nothing to configure.

When you want your own user records, add two callbacks. onLogin stores whoever just logged in and returns the id the cookie points at; getUser turns that id back into the user on every request:

import server from '@server/next';
import { db } from './db.js';

export default server({
  auth: {
    providers: 'github',
    // Find or create the person, and return the id the cookie will carry
    onLogin: (profile) => db.users.upsertByEmail(profile).id,
    // Turn that id back into the person, on every request
    getUser: (id) => db.users.find(id),
  },
});

No store to configure and no schema of ours: those two functions are the only places auth touches your data, so it works the same against Postgres, Redis, SQLite or anything else.

Open the page and click the link: the OAuth flow runs and you come back signed in.

3. Protect a route

The signed-in user is on ctx.user on every request (undefined when logged out), so a one-line middleware guards a route:

const requireUser = (ctx) => {
  if (!ctx.user) return 401;
};

export default server({ auth: 'cookie:github' })
  .get('/account', requireUser, (ctx) => `Your email: ${ctx.user.email}`);

4. Log out

POST /auth/logout clears the session:

<form method="POST" action="/auth/logout">
  <button>Log out</button>
</form>

Next steps

  • Swap the in-memory Map for a persistent store.
  • Add more providers with the object form: auth: { providers: ['github', 'google'] }. See Authentication.