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/socialGoogle, GitHub, Apple, Microsoft, Facebook, X, TikTok.
@atlas/authThe session or JWT you issue once they come back.
@atlas/serverRoutes, redirects, cookie headers.
@atlas/dbUsers, keyed by provider and provider id.
Start it
atlas init -n myapp --template api
What it looks like
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 })),
]