Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/content/docs/build/admin-dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Admin Dashboard App"
description: "seamless-auth-admin-dashboard is the operator-facing application for managing and observing a Seamless Auth deployment."
sidebar:
order: 5
order: 7
---

# Admin Dashboard
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/build/custom-auth-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "How To Build Your Own Auth UI"
description: "Use this guide when the built-in AuthRoutes screens work functionally, but your application needs its own layout, copy, validation, or route structure."
sidebar:
order: 6
order: 8
---

import { Aside } from '@astrojs/starlight/components';
Expand Down
17 changes: 15 additions & 2 deletions src/content/docs/build/ecosystem.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ seamless-auth-api
@seamless-auth/express or @seamless-auth/core
-> connects your backend to the auth engine

@seamless-auth/react
-> connects your frontend to the auth flow
@seamless-auth/react or @seamless-auth/react-native
-> connects your web or mobile frontend to the auth flow

seamless-auth-admin-dashboard
-> gives operators a UI for users, organizations, sessions, events, security, and config
Expand All @@ -44,6 +44,8 @@ seamless-auth-admin-dashboard
| `@seamless-auth/core` | Framework-agnostic server auth primitives | You are building your own backend adapter |
| `@seamless-auth/express` | Official Express adapter | Your backend is Express and you want the supported integration path |
| `@seamless-auth/react` | React SDK and built-in auth screens | Your frontend is React and you want a ready-to-use auth integration |
| `@seamless-auth/react-native` | React Native SDK: provider, hooks, native ports | Your frontend is an Expo or React Native app |
| `@seamless-auth/client` | The framework-free client the two SDKs share | You are building a binding for another framework |
| `seamless-auth-admin-dashboard` | Operator dashboard app | You want a UI for admins and support teams |
| `@seamless-auth/types` | Shared request/response and domain types | You want consistent types across apps and tooling |

Expand Down Expand Up @@ -105,6 +107,10 @@ with `@seamless-auth/core`.
The SDK gives you auth state and flow helpers, but your app still owns routing, authorization, and
product-specific UI decisions.

`@seamless-auth/react-native` gives an Expo or React Native app the same state and headless client
over bearer transport, with native passkeys and the platform keystore behind ports. It ships no
screens. See [React Native SDK](/build/react-native-sdk).

---

### 4. The Admin Dashboard Is A Separate App
Expand Down Expand Up @@ -160,6 +166,13 @@ Read [React SDK](/build/react-sdk) and

---

### I need to integrate a mobile app

Read [React Native SDK](/build/react-native-sdk) and [Mobile Passkeys](/build/mobile-passkeys).
`seamless init --mobile=expo` scaffolds an Expo app next to the web and API projects.

---

### I need OAuth, teams, or MFA

Read [How To Add OAuth Login](/build/oauth-login),
Expand Down
180 changes: 180 additions & 0 deletions src/content/docs/build/mobile-passkeys.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
---
title: "Mobile Passkeys"
description: "What a native app needs before passkeys and universal links work: an associated domain, the two association files, and the auth server's origins."
sidebar:
order: 6
---

# Mobile Passkeys

On the web a passkey is bound to the page's origin and the browser checks it. On a phone there is
no page, so the platform checks that the app is allowed to speak for the relying party's domain.
That check is what most of this page sets up. Get it wrong and every ceremony fails with a
generic error, on a real device and in the simulator alike.

---

## The Pieces

```txt
Relying party domain RPID on the auth server, e.g. app.example.com
|
+-- https://app.example.com/.well-known/apple-app-site-association
+-- https://app.example.com/.well-known/assetlinks.json
|
+-- iOS app entitlement associatedDomains: webcredentials:app.example.com
+-- Android app the signing certificate named in assetlinks.json
|
+-- auth server ORIGINS https://app.example.com and android:apk-key-hash:<hash>
```

The same association also gives you universal links, so a magic link emailed as
`https://app.example.com/verify-magiclink?token=...` opens the app.

---

## 1. Pick The Domain

The relying party is the auth server's `RPID`. Use the domain your web app is served from, so web
and native passkeys are the same credentials. The domain must be reachable over HTTPS with a
publicly trusted certificate; both platforms fetch the association files from it, and neither
accepts `localhost`, a private address, or a self-signed certificate.

For development, use a staging subdomain that hosts the files. Email codes and magic links still
work against a local stack; passkeys do not.

---

## 2. Host The Association Files

Both files live under `/.well-known/` on the domain, served as `application/json` with no
redirect.

`apple-app-site-association` (no extension):

```json
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID.com.example.app"],
"components": [{ "/": "/verify-magiclink*" }, { "/": "/oauth/callback*" }]
}
]
},
"webcredentials": { "apps": ["TEAMID.com.example.app"] }
}
```

`webcredentials` is what passkeys need; `applinks` is what universal links need. The Team ID is
the one on the signing certificate (its `OU`), which is not always the team shown in App Store
Connect for an enterprise or a transferred app.

`assetlinks.json`:

```json
[
{
"relation": [
"delegate_permission/common.handle_all_urls",
"delegate_permission/common.get_login_creds"
],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": ["AB:CD:..."]
}
}
]
```

`get_login_creds` is what passkeys need; `handle_all_urls` is what app links need. The
fingerprint is the SHA-256 of the signing certificate: your debug keystore's for a development
build, the upload or Play signing key's for a store build. List each one that will sign the app.

The Expo template ships `tools/associations/generate.mjs`, which writes both files from the team
ID, bundle ID, package name and fingerprint, and prints the Android origin for the next step.

---

## 3. Extend The Auth Server's Origins

Every passkey ceremony reports the origin it ran in, and the auth server refuses one it does not
know. Add the native origins to `ORIGINS` (or the `origins` system config key), keeping the web
origin first:

```txt
ORIGINS=https://app.example.com,android:apk-key-hash:q1w2e3...
```

- iOS reports the relying party as `https://<rpid>`, which is usually your web origin already.
- Android reports `android:apk-key-hash:<hash>`, where the hash is the base64url encoding of the
same SHA-256 certificate fingerprint, with the colons removed and the bytes decoded. One entry
per signing certificate.
- The web origin stays first. `origins[0]` is the fallback the auth server uses when a request
gives no origin of its own, and an `android:` entry there would break web ceremonies.

See [System Config Reference](/reference/system-config) for `origins` and `rpid`.

---

## 4. Declare The Association In The App

Expo (`app.json`):

```json
{
"expo": {
"ios": {
"bundleIdentifier": "com.example.app",
"associatedDomains": ["webcredentials:app.example.com", "applinks:app.example.com"]
},
"android": {
"package": "com.example.app",
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "app.example.com", "pathPrefix": "/verify-magiclink" }],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
```

Rebuild the development client after changing either; the entitlement is baked in at build time.

---

## 5. Check It

- `curl -sI https://app.example.com/.well-known/apple-app-site-association` answers `200` with a
JSON content type and no redirect. Apple fetches through its CDN, so a change can take a while
to reach a device; the simulator fetches directly.
- On a simulator build, the entitlement is in the binary's `__TEXT,__entitlements` section rather
than a provisioning profile. `otool -s __TEXT __entitlements MyApp.app/MyApp` shows it, and is
the quickest way to confirm a rebuild picked it up.
- On Android, `adb shell pm get-app-links com.example.app` shows whether the domain verified.
- A ceremony that fails immediately with a dismissal-shaped error on a device that never showed a
prompt is almost always the association: a stale file, the wrong Team ID, or the wrong
fingerprint for the build you are running.

---

## Rate Limits Behind Carrier NAT

Phones on mobile networks share a small pool of public addresses. The auth API's per-IP limits on
OTP, magic link and OAuth starts are configurable through the `flow_rate_limits` system config
key, and a mobile audience usually needs them wider than the defaults, which were chosen for
browsers. Per-identity limits stay as they are.

---

## Related Docs

- [React Native SDK](/build/react-native-sdk)
- [Server SDKs](/build/server-sdks)
- [System Config Reference](/reference/system-config)
- [Environment Variables](/reference/environment-variables)
2 changes: 1 addition & 1 deletion src/content/docs/build/oauth-login.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "How To Add OAuth Login"
description: "Use this guide when you want users to sign in with an external OAuth provider while still letting Seamless Auth own local users, sessions, cookies, and…"
sidebar:
order: 8
order: 10
---

import { Aside } from '@astrojs/starlight/components';
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/build/organizations-memberships.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "How To Use Organizations And Memberships"
description: "Use this guide when your app needs team, workspace, tenant, or customer-account boundaries."
sidebar:
order: 9
order: 11
---

# How To Use Organizations And Memberships
Expand Down
Loading
Loading