Skip to content

Web SSO (desktop Apple/Google login)

General RP integration is a separate track

This page covers the desktop web SSO mechanism by which logi uses Apple/Google as upstream IdPs.

Two entry points

HostUserDestination
api.1pass.dev/session/newEnd user/oauth/authorize?... (RP consent)
start.1pass.dev/console/loginDeveloper/developer (cross-host handoff)

Flow

User browser

      ▼  ① Click "Continue with Apple/Google"
api.1pass.dev/auth/{apple,google}/web/start?return_to=<path>
      │  ② Issue an OauthWebState row (state, nonce, return_to, 10 min TTL)
      │  ③ 302 to the Apple/Google authorization endpoint

appleid.apple.com  /  accounts.google.com
      │  ④ User logs in + consents
      ▼  ⑤ Callback
api.1pass.dev/auth/{apple,google}/web/callback
      │  ⑥ Consume the state once (provider-scoped)
      │  ⑦ Exchange code → id_token
      │  ⑧ Verify id_token against the JWKS (alg, iss, aud, exp, nonce, sub, email_verified)
      │  ⑨ WebSsoUserResolver: find by sub → email merge → create
      │  ⑩ start_new_session_for → issue a Session

        ┌─────────── Is return_to /oauth/authorize?
        │  YES                                    NO (console path)
        ▼                                          ▼
Set api.1pass.dev cookie                  Issue a SessionHandoffToken (30s TTL)
Redirect to return_to                     Delete the api.1pass.dev cookie
                                          start.1pass.dev/session/handoff?token=...

                                          start.1pass.dev consumes the token
                                          Issues its own host cookie
                                          Redirects to return_to

Routes

ControllerRoute
Web::GoogleSsoControllerGET /auth/google/web/start, GET /auth/google/web/callback
Web::AppleSsoControllerGET /auth/apple/web/start, POST /auth/apple/web/callback (form_post, CSRF skip)
SessionHandoffsControllerGET /session/handoff (console-host-constrained)

Console setup

Apple Services ID

  1. Identifiers → Services IDs → +
  2. Description: logi Web SSO
  3. Identifier: dev.1pass.web (= APPLE_WEB_SERVICES_ID)
  4. Sign in with Apple → Configure
    • Primary App ID: com.dcodelabs.logi (the same SIWA key as the native bundle also signs the web client_secret JWT)
    • Domains: api.1pass.dev
    • Return URLs: https://api.1pass.dev/auth/apple/web/callback

Google OAuth Web Client

  1. APIs & Services → Credentials → + CREATE CREDENTIALS → OAuth client ID
  2. Application type: Web application
  3. Name: logi Web SSO
  4. Authorized JavaScript origins: https://api.1pass.dev
  5. Authorized redirect URIs: https://api.1pass.dev/auth/google/web/callback
  6. OAuth Consent Screen → PUBLISH APP (in Testing mode, only registered test users can sign in)

ENV (Render logi-server)

APPLE_WEB_SERVICES_ID=dev.1pass.web

# Apple SIWA key (shared between native + web)
APPLE_TEAM_ID=<your Apple Developer Team ID>
APPLE_KEY_ID=<Key ID of your SIWA key>
APPLE_PRIVATE_KEY=<.p8 PEM contents — or mount via APPLE_PRIVATE_KEY_PATH>

GOOGLE_WEB_CLIENT_ID=<public Client ID>
GOOGLE_WEB_CLIENT_SECRET=<Client Secret>

⚠️ Always update Render env vars with an individual PUT (never a Collection PUT). The MCP update_environment_variables tool is safe because it defaults to replace=false (merge).

Security invariants

  • State is a server-side OauthWebState row: single-use, provider-scoped, 10 min TTL.
  • return_to is validated at /start and stored in the DB; the callback uses only the row value (it does not trust the parameter).
  • safe_return_to? does a path-exact match (/oauth/authorize, /console, /console/..., etc.).
  • ID token: alg whitelist (ES256, RS256), kid required, iss/aud exact, exp+iat skew 60s, nonce, sub non-empty, Google azp (when present) exact, Google email_verified=true enforced.
  • The Apple nonce uses direct equality (unlike the SHA256 hashing in native).
  • The JWKS forces a refresh on an unknown kid (to avoid key-rotation outages).
  • /start rate limit 30/min/IP.
  • After the callback, reset_sessionstart_new_session_for (session fixation defense).
  • Apple user form field: 2KB raw cap + 128B per name part.
  • Pending-purge / locked / suspended accounts are blocked (AccountPendingPurge, AccountLockedOrSuspended, ProviderSubConflict).
  • The session cookie domain: is host-only (keeps embed.1pass.dev isolated).
  • The handoff endpoint return_to whitelist allows only /console, /developer, /demo (/oauth/authorize is rejected).

Operational procedures

Rotating the Google Client Secret

  1. Google Cloud Console → Credentials → the OAuth Client → Add Secret
  2. Update the Render env var GOOGLE_WEB_CLIENT_SECRET (merge)
  3. Deploy and confirm login works
  4. Disable the old secret

Rotating the Apple .p8

  1. Apple Developer → Keys → create a new key (SIWA capability)
  2. Render env: update APPLE_KEY_ID + APPLE_PRIVATE_KEY
  3. Deploy and confirm login works
  4. Revoke the old key

Pending-purge users

If a user within the soft-delete grace period tries to re-register via web SSO, they are blocked with AccountPendingPurge. Retry after the mobile-app recovery flow (POST /api/v1/account_recoveriesconsume).

Troubleshooting

SymptomCauseFix
Returned to the login page after console SSOSessionHandoffToken not issued or expired (30s)grep [session_handoff] failure: in the Render logs
Google access_deniedConsent screen in Testing modeOAuth consent screen → PUBLISH APP
Apple invalid_clientThe Bundle ID was used as client_idConfirm APPLE_WEB_SERVICES_ID=dev.1pass.web
jwks: unknown kid after refreshKey rotation or token forgeryForce-invalidate auth/jwks_cache/{apple_web,google} in Rails.cache and retry
Increased frequency of state mismatch10 min TTL exceeded or multi-tabCheck the TTL. Because state is provider-scoped, multi-tab is safe

Known limitations

  • Mobile UAs do not show the SSO card — the native app flow (logi:// + Universal Links) is canonical.
  • Passkey is not enforced immediately after console SSO (advisory).
  • Cross-provider merge is not possible for Apple private relay (it can only be unified via a native device link).
  • Login method restriction — restrict the SSO/login methods an RP surfaces on the login screen (the provider parameter · console setting)

Identity가 제품의 신뢰를 만듭니다.