Skip to content

Auth Verify (Magic Link Token Verification)

Metadata

  • System type: flow

System Intent

  • What this is: end-to-end magic-link verification across three layers — the landing-web page verifies the token with the server, hands off the code via deep link to the mobile app, and the mobile app auto-submits the code to complete sign-in.

Mermaid Diagram

flowchart TD
  Email["User clicks magic link in email"] --> Web["landing-web /auth/verify\n(+page.svelte)"]
  Web -->|POST /auth/verify\nemail + token + session| Lambda["auth/verify Lambda\n(app.py)"]
  Lambda -->|Cognito challenge response| Cognito["AWS Cognito"]
  Cognito -->|AuthenticationResult| Lambda
  Lambda -->|create_handoff_code| DB["PostgreSQL"]
  Lambda -->|handoffCode + expiresIn| Web
  Web -->|window.location = encache://auth/verify?code=...| App["Mobile app\n(verify.tsx)"]
  Web -->|fallback: display code manually| Manual["User copies code"]
  App -->|auto-submit via deepLinkCode param| Complete["completePasswordlessSignInWithCode"]
  Manual -->|user types code| Complete

Flows

Flow: deepLinkVerify

  • Core files: apps/landing-web/src/routes/auth/verify/+page.svelte, main/app/app/(auth)/verify.tsx, main/server/api/auth/verify/app.py

Types

VerifyRequest {
  email: string (required)
  token: string (required — from magic link query param)
  session: string (required — from magic link query param)
}

HandoffCodeResponse {
  handoffCode: string (short-lived opaque code)
  expiresIn: number (seconds until code expires)
}

StandardError {
  status: number (HTTP status)
  code: string (stable machine-readable code)
  message: string (human-readable summary)
}

Paths

path input output path-type notes
deepLinkVerify.success VerifyRequest deep link opens app with code param happy path web page redirects to encache://auth/verify?code=<handoffCode>; app populates TextInput with code via setCode(), then code-watching effect auto-submits; user sees the code in the input before sign-in completes
deepLinkVerify.fallback VerifyRequest manual code display on web page fallback used when app is not installed or device does not support encache:// scheme
deepLinkVerify.missing-params malformed URL 400 AUTH_TOKEN_REQUIRED / AUTH_SESSION_REQUIRED error web page shows generic error
deepLinkVerify.invalid-token wrong or expired token 400 AUTH_TOKEN_INVALID error Cognito rejects the challenge response
deepLinkVerify.invalid-email bad email format 400 AUTH_EMAIL_INVALID error

Pseudocode

# landing-web (+page.svelte) — runs onMount
params = parseQueryString(window.location.search)   # email, token, session
response = POST /auth/verify { email, token, session }
if not response.ok or not response.data.handoffCode:
  status = "error"
  return
handoffCode = response.data.handoffCode
window.history.replaceState(...)  # strip sensitive params from URL
deepLink = "encache://auth/verify?code=" + encodeURIComponent(handoffCode)
window.location.href = deepLink   # attempt to open app; silently ignored on desktop
status = "code"                   # show manual fallback UI regardless

# mobile app (verify.tsx) — useEffect on deepLinkCode (autofill effect)
deepLinkCode = useLocalSearchParams().code
if deepLinkCode matches /^\d{6}$/:
  setCode(deepLinkCode)           # populate TextInput so user sees the code

# mobile app (verify.tsx) — useEffect on code (submission effect)
trimmed = code.trim()
if trimmed.length < 6: lastSubmittedRef = ""; return
if not /^\d{6}$/.test(trimmed): return
if submitting or success: return
if lastSubmittedRef == trimmed: return   # guard against double-submission
lastSubmittedRef = trimmed
handleVerify(trimmed)

# mobile app — useEffect on deepLinkCode (focus effect)
if not deepLinkCode:
  focus(inputRef)                 # focus manual input when no deep-link code present

# mobile app — handleVerify
if not /^\d{6}$/.test(trimmedCode):
  setError(...)
  return
await completePasswordlessSignInWithCode(trimmedCode)

Logs

Source Location
auth/verify Lambda CloudWatch: /aws/lambda/encache-auth-verify

Deployment

  • Mechanism: SAM (Lambda), SvelteKit (landing-web), Expo (mobile app)
  • Notes: PUBLIC_API_URL must be set in the landing-web environment for the POST to reach the correct API Gateway endpoint.

Error Cases

Condition Response
Invalid email format 400 AUTH_EMAIL_INVALID
Missing token 400 AUTH_TOKEN_REQUIRED
Missing session 400 AUTH_SESSION_REQUIRED
Wrong or expired token 400 AUTH_TOKEN_INVALID
Deep link not supported (desktop/app not installed) Manual code display fallback shown on web page
deepLinkCode param not a 6-digit number Mobile ignores it; manual input field is focused instead