Web field Plate 95 Route §95
Plate 95

The API with social login

Someone else stores the password. You store a provider name and an id, and you get to skip the entire reset-email apparatus.

PKCE and the state parameter ride in a signed HttpOnly cookie, so the flow is stateless — no session table, no schema, nothing to sweep. Add providers by adding entries to the map.

Apple is the exception: it delivers the callback as form_post, so its route is a POST behind parseForm while every other provider is a GET.

What you are carrying

@atlas/auth/social

Google, GitHub, Apple, Microsoft, Facebook, X, TikTok.

@atlas/auth

The session or JWT you issue once they come back.

@atlas/server

Routes, redirects, cookie headers.

@atlas/db

Users, keyed by provider and provider id.

Start it

atlas init -n myapp --template api

What it looks like

src/social.tsts
import { socialAuth, google, github } from "@atlas/auth/social"
import { get, redirect, putHeader } from "@atlas/server"

const social = socialAuth({
  secret: process.env.OAUTH_STATE_SECRET!,
  cookie: { secure: process.env.NODE_ENV === "production" },
  providers: {
    google: google({ clientId, clientSecret, redirectUri: `${origin}/auth/google/callback` }),
    github: github({ clientId, clientSecret, redirectUri: `${origin}/auth/github/callback` }),
  },
})

const onSuccess = async (c, { profile }) => {
  const user = await upsertUserFromProfile(profile)  // keyed by (provider, providerId)
  const jwt = await token.sign({ id: user.id }, secret, { expiresIn: 86400 })
  return redirect(putHeader(c, "set-cookie", `session=${jwt}; HttpOnly; Path=/`), "/")
}

export const routes = [
  get("/auth/google", social.start("google")),
  get("/auth/google/callback", social.callback("google", { onSuccess })),
]

Read next

@wess/atlas/auth Cookbook Quick start
Type to search guides and package references.