Hosted Links
Use an Own Auth URL for magic links, verification emails, password resets, and invites. Own Auth hosts the link pages for you, then users continue to your configured website or app.
When to use hosted links
Most web apps handle auth links on their own domain. The user clicks a link to https://yourapp.com/auth/verify?token=... and your server handles it.
But if you're building a mobile app, a desktop app, or a side project without a web server, you don't have a URL to receive those clicks. Hosted links solve this.
Instead of pointing to your domain, auth emails link to https://go.own-auth.com/your-app/.... The hosted page opens your app via a deep link, passing the token along. Your app sends the token to your backend, and own-auth handles the rest.
User clicks email link
-> https://go.own-auth.com/your-app/magic?token=abc123
-> hosted page opens deep link: myapp://auth/magic?token=abc123
-> your app sends token to your backend
-> own-auth verifies token, creates session
-> user is signed inThe hosted page is a bridge. It doesn't verify tokens, create sessions, or touch your data. Your backend does all of that through the own-auth package, same as always.
Setup
1. Open Your App Settings
- Open the Own Auth dashboard, select your app, and open the Settings tab.
2. Switch to Hosted Mode
Under Link mode, select Own Auth hosted. The dashboard shows the hosted page and destination settings.
- Your hosted page: the read-only base URL for the app's hosted links
- Destination URL: the website or app opened after the hosted page
- Fallback URL: an optional store or download URL for app-scheme destinations
3. Set Your Destination URL
Enter an HTTPS website, local development URL, or app scheme. The hosted page opens this destination after the user clicks the email link.
https://app.example.com/auth
myapp://auth/callbackThe hosted page appends the flow type and token automatically:
https://app.example.com/auth/magic?token=abc123
myapp://auth/callback/magic?token=abc123
myapp://auth/callback/verify?token=abc123
myapp://auth/callback/reset?token=abc123
myapp://auth/callback/invite?token=abc123For a website destination, handle these paths in the website. For an app destination, make sure the app is registered to handle it. On iOS, add a custom URL scheme in Info.plist, or configure Universal Links with Associated Domains. On Android, add an intent filter in AndroidManifest.xml for the custom scheme or App Link.
Test an app-scheme link on a phone or desktop device where the standalone app is installed. A mobile app scheme cannot open the mobile app from a desktop browser, and Expo Go may not register your app's custom scheme. Use a development or production build when testing.
For app-scheme destinations, the hosted page detects common iOS and Android user agents. Mobile browsers attempt the deep link. Desktop browsers skip the deep link and tell the user to open the email on their phone. HTTPS destinations redirect on every device.
Opening the hosted link on desktop does not consume the one-time token. The hosted page does not verify it or call an auth API, so the same email link still works when the user opens it on their phone.
4. Set a Fallback URL (Optional)
For an app-scheme destination, the hosted page can show a button to a store or download page when the app does not open on mobile. Website destinations and the desktop state do not use a fallback URL.
https://apps.apple.com/app/your-app/id123456789Leave this empty if the app does not need an install fallback.
Configure Through the API
The same settings can be updated through the Delivery Management API. Authenticate with an account API key. Use hosted for Own Auth hosted link mode, set deepLinkScheme to the website or app destination, and omit fallbackUrl or send null when no fallback is needed.
curl -sS -X PATCH https://api.own-auth.com/v1/apps/app_x1y2z3/settings \
-H "authorization: Bearer oa_your_api_key" \
-H 'content-type: application/json' \
-d '{
"linkMode": "hosted",
"deepLinkScheme": "coparent://app/auth",
"fallbackUrl": "https://apps.apple.com/app/id123456789"
}'Handling Destination Links
The hosted page forwards a flow type and one-time token. The website or app reads both values and sends them to its backend. The backend then calls the matching own-auth method. The examples below use an app scheme; an HTTPS destination receives the same paths and query parameter.
Magic Link Sign-In
The user requested a magic link. Send the token to your backend, verify it, and create a session.
// App opened: myapp://auth/callback/magic?token=abc123
const token = getTokenFromDeepLink();
// In the backend handler after the app sends the token:
const { user, session, sessionToken } = await auth.verifyMagicLink({ token });
// Return the session token to the app and store it securely.Email Verification
The user signed up and needs to verify their email. Send the token to your backend and mark the email as verified.
// App opened: myapp://auth/callback/verify?token=abc123
const token = getTokenFromDeepLink();
// In the backend handler:
await auth.verifyEmail({ token });
// Tell the app to show an Email verified confirmation.Password Reset
Show a set-new-password form in your app. Send the new password and token to your backend after the user submits it.
// App opened: myapp://auth/callback/reset?token=abc123
const token = getTokenFromDeepLink();
// In the backend handler after the app submits the form:
await auth.resetPassword({
token,
newPassword: passwordFromForm
});
// Tell the app to ask the user to sign in with the new password.Do not automatically sign the user in after a password reset. Let them sign in manually with the new password.
Invite Acceptance
The user was invited to join an organisation. Send the token to your backend and accept the invitation.
// App opened: myapp://auth/callback/invite?token=abc123
const token = getTokenFromDeepLink();
// In the backend handler:
const { organisation, member } = await auth.acceptInvite({
token,
userId: currentUser.id
});
// Tell the app to show a confirmation screen.The invited person must sign in or create an account first. Pass the signed-in user's ID when accepting the invite.
Error Handling
Token errors happen in the backend after the destination submits the token. The hosted page only redirects and does not know whether the token is valid.
import { AuthError } from "own-auth";
try {
const result = await auth.verifyMagicLink({ token });
} catch (error) {
if (error instanceof AuthError) {
switch (error.code) {
case "expired_token":
// This link has expired. Request a new one.
break;
case "token_already_used":
// This link has already been used.
break;
case "invalid_token":
// This link is not valid.
break;
}
}
}These token error codes also apply to email verification, password reset, and invitation flows. Invitation acceptance can additionally return invitation_not_found or invitation_not_pending.
How Emails Know to Use Hosted Links
When an app uses Own Auth hosted mode, Own Auth Delivery replaces the normal destination with the correct hosted URL before it queues the email. The own-auth email request code does not change.
// This call is the same in both link modes.
await auth.requestMagicLink({ email: "alice@example.com" });The selected app's dashboard settings determine the link mode. Your application code does not choose it per request.
Switching modes
You can switch between My URLs and Own Auth hosted at any time in the dashboard. Outstanding emails still use the link mode that was active when they were sent. New emails use the new mode.
Switching modes doesn't affect tokens, sessions, or user data. Only the URL in future emails changes.
Security
Hosted links keep authentication and user data in your backend:
- No token verification on the hosted page. It redirects without calling an auth API or validating the token.
- No session tokens in the destination URL. It contains a one-time auth token. The backend creates the session after verification.
- Tokens are one-time.
own-authconsumes a token on first use, and a second use fails withtoken_already_used. - Token storage is protected by your OWN_AUTH_TOKEN_PEPPER. Raw tokens are not recoverable from the stored hashes.
- Hosted pages use HTTPS. The configured destination can be an HTTPS website, local development URL, or app scheme.
Limitations
- Custom branding is limited. The hosted page shows the app name and Powered by
own-auth. Use My URLs when full control over the link page is required. - App-scheme detection is imperfect. Browsers and operating systems handle app schemes differently, so the hosted page provides a retry and optional store fallback.
- A mobile app scheme does not open the mobile app from a desktop browser. Desktop users are told to open the email on their phone. Configure a fallback URL for mobile users who do not have the app installed, and test the scheme using an installed standalone build rather than Expo Go.
- Password reset requires your own form. The hosted page cannot access your database, so your app must show its own set-new-password screen.