# §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

```bash
atlas init -n myapp --template api
```

## What it looks like

`src/social.ts`

```ts
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 })),
]
```

## Where now

- Put it online — turn to §100 (Appendix A)
- Hand it to an agent — turn to §102 (Appendix C)
- Walk it again from the start — turn to §1
