---
phase: 8
slug: social-sign-in
status: verified
threats_open: 0
asvs_level: 1
created: 2026-05-15
updated: 2026-05-15
register_authored_at_plan_time: true
---

# Phase 8 — Security

> Threat verification for Phase 8 social sign-in after UAT scope change: Google sign-in remains active; Apple sign-in is disabled and hidden.

---

## Trust Boundaries

| Boundary | Description | Data Crossing |
|----------|-------------|---------------|
| Browser to Xtablo auth routes | User starts Google sign-in and returns through callback routes | OAuth state, nonce, authorization code, session cookies |
| Xtablo to Google OAuth/OIDC | Server exchanges code and verifies ID token | Authorization code, client credentials, ID token claims |
| Xtablo auth to Postgres | Server creates or links local users and provider identities | User email, provider subject, provider metadata, session rows |
| Auth UI to email/password forms | Social provider controls coexist with existing CSRF-protected forms | CSRF token, form fields, provider start links |
| Authenticated account page to identity data | User views linked provider status | Current user's provider identity rows only |

---

## Threat Register

| Threat ID | Category | Component | Disposition | Mitigation | Status |
|-----------|----------|-----------|-------------|------------|--------|
| T-08-01 | Tampering / spoofing | `user_identities` linking | mitigate | `user_identities` has unique `(provider, provider_subject)` and `linkProviderUser` looks up provider subject inside a transaction before email matching. Evidence: `backend/migrations/0006_social_identities.sql`, `backend/internal/web/handlers_social.go`. | closed |
| T-08-02 | Spoofing | social-only signup conflict | mitigate | Signup blocks duplicate email/password signup for NULL-password social-only users with provider guidance. Evidence: `IsSocialOnlyUserByEmail`, `SignupPostHandler`, `TestSignup_SocialOnlyExistingUserShowsProviderMessage`. | closed |
| T-08-03 | Elevation of privilege | provider profile storage | mitigate | Display name and avatar are stored only as metadata in `user_identities`; authorization and session identity use local `users.id`. Evidence: `InsertUserIdentity`, `linkProviderUser`. | closed |
| T-08-04 | CSRF / spoofing | Google callback | mitigate | Callback validates OAuth state cookie before token exchange or linking. Evidence: `GoogleCallbackHandler`, `TestGoogleCallbackInvalidStateRejectedBeforeExchange`. | closed |
| T-08-05 | Spoofing / tampering | Google ID token | mitigate | OIDC verifier validates issuer, audience, expiry, and subject; handler requires verified email and nonce. Evidence: `auth.OIDCVerifier.Verify`, `GoogleCallbackHandler`, `TestGoogleCallbackUnverifiedEmailRejected`. | closed |
| T-08-06 | Elevation of privilege | Account linking | mitigate | Existing provider subject wins first; new link by email only occurs after verified provider email and inside one transaction. Evidence: `linkProviderUser`, `TestGoogleCallbackExistingSubjectWinsWhenEmailChanges`, `TestGoogleCallbackEmailUpdateConflictDoesNotRelinkSubject`. | closed |
| T-08-07 | Information disclosure | Logging | mitigate | OAuth handlers log structured error categories only; auth codes, ID tokens, access tokens, and client secrets are not logged or rendered. Evidence: `handlers_social.go` and generic provider UI error constants. | closed |
| T-08-08 | Session fixation | Social callback session issuance | mitigate | Successful provider callback creates a normal server-managed Xtablo session cookie through `auth.Store.Create` and `auth.SetSessionCookie`. Evidence: `GoogleCallbackHandler`, session tests. | closed |
| T-08-09 | CSRF / spoofing | Apple callback | mitigate | Apple callback is not mounted after UAT scope change; direct `/auth/apple/start` and `/auth/apple/callback` return 404. Evidence: `router.go`, `TestAppleRoutesAreDisabled`. | closed |
| T-08-10 | Spoofing / tampering | Apple ID token | mitigate | Apple sign-in is disabled in the product surface; no Apple ID token is accepted by mounted routes. Evidence: `router.go`, `cmd/web/main.go`, `TestAppleRoutesAreDisabled`. | closed |
| T-08-11 | Information disclosure | Apple client secret | mitigate | Runtime no longer reads `APPLE_*` env vars or generates Apple client secrets. Evidence: `cmd/web/main.go`, `backend/.env.example`, `backend/README.md`. | closed |
| T-08-12 | Elevation of privilege | Apple first-login name | mitigate | Apple sign-in is disabled, so Apple names cannot affect local identity state. Evidence: unmounted Apple routes and no Apple auth UI. | closed |
| T-08-13 | CSRF middleware interaction | Apple callback / local forms | mitigate | Apple callback route is removed; local POST routes remain under existing CSRF middleware. Evidence: `router.go`, `auth.Mount` ordering tests. | closed |
| T-08-14 | Tampering | Provider buttons | mitigate | Enabled Google control links only to `GET /auth/google/start`; disabled Google control has no `href`; Apple controls are not rendered. Evidence: `auth_login.templ`, `AuthProviderButtonsBlock`, auth page tests. | closed |
| T-08-15 | Information disclosure | UI error copy | mitigate | User-facing provider errors are generic and do not expose token values, codes, claim names, or secrets. Evidence: `providerGenericError`, `providerEmailUnverified`, auth page tests. | closed |
| T-08-16 | CSRF | Auth form regression | mitigate | Provider control is an anchor or disabled button and does not submit email/password forms; existing forms keep CSRF fields and POST handlers. Evidence: `auth_login.templ`, `auth_signup.templ`, `TestLoginProviderButtonsConfigured`, `TestSignupProviderButtonsConfigured`. | closed |
| T-08-17 | Information disclosure | Linked providers view | mitigate | Route is inside `auth.RequireAuth`; query is scoped to current `user.ID`; rendered rows now only include Google. Evidence: `router.go`, `AccountProvidersHandler`, `TestLinkedProviders_UnauthRedirectsToLogin`. | closed |
| T-08-18 | Information disclosure | Env docs | mitigate | `.env.example` uses placeholders only and no real provider secrets; Apple env placeholders were removed after disablement. Evidence: `backend/.env.example`, `backend/README.md`. | closed |
| T-08-19 | Regression drift | Auth/social regression suite | mitigate | Full backend regression passed after Apple disablement and Google/social changes. Evidence: `go test ./... -count=1` run on 2026-05-15, `08-UAT.md` 7/7 passed. | closed |

---

## Accepted Risks Log

No accepted risks.

---

## Security Audit Trail

| Audit Date | Threats Total | Closed | Open | Run By |
|------------|---------------|--------|------|--------|
| 2026-05-15 | 19 | 19 | 0 | Codex inline security audit |

---

## Notes

- Apple implementation primitives still exist in `backend/internal/auth/oauth.go` and unit tests, but the web product surface does not wire Apple config, render Apple controls, or mount Apple auth routes.
- The active provider surface for Phase 8 is Google only.

---

## Sign-Off

- [x] All threats have a disposition (mitigate / accept / transfer)
- [x] Accepted risks documented in Accepted Risks Log
- [x] `threats_open: 0` confirmed
- [x] `status: verified` set in frontmatter

**Approval:** verified 2026-05-15
