Cross-Domain (Well-Known)
⚡ 5 min readWebAuthn credentials are scoped to an RP ID (your domain). When your app lives on a subdomain (app.example.com) but the RP ID is the root domain (example.com), browsers perform a cross-origin check by fetching:
GET https://example.com/.well-known/webauthnThe response lists the origins that are authorised to use passkeys registered under that RP ID. Transcodes generates this JSON for your project. You expose it from your own domain in one of two ways.
Option A — Static file
Download the JSON from the Console → Authentication Cluster → Installation Guide and place it in your project. The file path differs slightly per framework, but all of them serve the public/ directory at the site root automatically — no extra config needed.
React (Vite)
my-react-app/
├── public/
│ └── .well-known/
│ └── webauthn ← put the file here (no extension)
├── src/
└── vite.config.ts{ "origins": ["https://app.example.com"] }Served at: https://yourdomain.com/.well-known/webauthn
The file has no extension — it is named webauthn, not webauthn.json. Most OS file explorers hide dotfiles by default; make sure .well-known/ is not gitignored.
Pros: Zero runtime cost, works with any static host.
Cons: You must re-download and redeploy the file when origins change in the Console.
Option B — Dynamic proxy
Proxy /.well-known/webauthn to the Transcodes API so it always reflects the latest Console settings — no file to maintain.
Each framework handles this differently: Vite (React, Vue, Vanilla) uses the dev-server proxy option in vite.config.ts, while Next.js uses rewrites() in next.config.ts which also works in production.
Vite proxy spec: target must be the base URL only (e.g. https://transcodesapis.com). rewrite must return the path only — not a full URL. Vite prepends target to whatever rewrite returns, so returning a full URL produces a double URL like https://transcodesapis.com/https://transcodesapis.com/v1/....
✅ target: 'https://transcodesapis.com'
rewrite: () => '/v1/project/abc/well-known/webauthn'
❌ rewrite: () => 'https://transcodesapis.com/v1/project/abc/well-known/webauthn'Framework examples
The proxy setup differs per framework. Pick your stack:
React (Vite)
React + Vite — vite.config.ts dev-server proxy. Dev only; use Option A or a server proxy for production static builds.
VITE_TRANSCODES_API_URL=http://localhost:3500
VITE_TRANSCODES_PROJECT_ID=your_project_idimport { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [react()],
server: {
proxy: {
'/.well-known/webauthn': {
target: env.VITE_TRANSCODES_API_URL, // base URL only
rewrite: () => // path only — no hostname
`/v1/project/${env.VITE_TRANSCODES_PROJECT_ID}/well-known/webauthn`,
changeOrigin: true,
},
},
},
};
});Replace {TRANSCODES_API_URL} with the API base URL from your Transcodes Console → Authentication Cluster → Installation Guide. Never commit production secrets to source control.
Which option should I use?
| Option A (static file) | Option B (proxy) | |
|---|---|---|
| Maintenance | Re-download on origin change | Automatic (always fresh) |
| Runtime cost | None | Tiny (proxied once per browser session) |
| Works offline / static host | ✅ | ❌ needs a Node server |
| Recommended for | Simple deploys, CDN-only | Vite dev server, Next.js, any Node host |
Verify it works
After deploying, check the endpoint in your browser or with curl:
curl https://yourdomain.com/.well-known/webauthn
# Expected: {"origins":["https://app.yourdomain.com",...]}The endpoint must be served from the RP ID domain (e.g. example.com), not from the subdomain where your app runs (app.example.com). Check your Configuration for the RP ID you have set.
Related
- Configuration — RP ID and domain settings
- Installation Guide — SDK setup
- JSON Web Key — server-side JWT verification