The core concept
The login page decides the OAuth flow based on user agent. When your login page loads, check if it’s running in Despia:Two complete flows
Web flow (standard OAuth)
When:userAgent doesn’t include ‘despia’
Flow:
- Login page → redirects to OAuth provider
- OAuth provider → redirects back to
/auth /authpage → sets session, navigates to/dashboard
Despia native flow
When:userAgent includes ‘despia’
Flow:
- Login page → calls
despia('oauth://...')to open native browser - OAuth provider → redirects to
/native-callback(in native browser) /native-callback→ extracts tokens, redirects to deeplink withoauth/prefix- Native app → intercepts deeplink, closes browser, navigates to
/auth /authpage → receives tokens from URL, sets session
Key differences
| Step | Web Flow | Despia Flow |
|---|---|---|
| Login page | window.location.href = oauthUrl | despia('oauth://?url=...') |
| OAuth redirect | → /auth | → /native-callback |
| Callback action | Set session, navigate | Redirect to deeplink |
| Deeplink | None | yourappdeeplink://oauth/auth?tokens |
| Browser closes | N/A | When deeplink called |
| Final landing | /auth (already there) | /auth (via deeplink) |
Apple Sign-In special handling
Apple Sign-In on iOS devices needs different handling:Troubleshooting
Browser session doesn’t open
Problem: User clicks login, nothing happens. Check:- OAuth URL not properly encoded: Use
encodeURIComponent() - Missing
yourappdeeplink://prefix:despia('oauth://?url=...')not justdespia(url) - Testing in web browser instead of Despia app
Provider blocks login inside the app
Problem: The provider’s login page opens inside your app’s WebView instead of a secure browser. Google shows403: disallowed_useragent. Other providers show a blank page, a security warning, or refuse to load.
Cause: On the login page the Despia branch redirects the WebView straight to the provider URL with window.location.href instead of opening a secure browser session.
In the Despia flow you never send the provider URL to the WebView. Google and Apple block OAuth inside embedded WebViews, and most providers’ terms forbid it. Always hand the URL to oauth://, which opens ASWebAuthenticationSession on iOS or Chrome Custom Tabs on Android, a session the provider trusts.
Wrong:
oauth://.
Browser doesn’t close after login
Problem: User completes OAuth, stuck in browser. Cause: Missingoauth/ prefix in deeplink.
Wrong:
oauth/ prefix in the deeplink is what tells Despia to close ASWebAuthenticationSession/Chrome Custom Tabs.
Tokens not reaching /auth page
Problem: Browser closes but user not logged in. Check /auth page:- Tokens not encoded in callback: Use
encodeURIComponent(token)when building deeplink - Reading from wrong place: Tokens are in query params (
?), not hash (#) - Not decoding: Use
decodeURIComponent()when parsing
Redirect URI mismatch
Problem: OAuth provider shows “redirect_uri mismatch” or “invalid redirect URI” error. Cause: The callback URL your code sends is not registered with the OAuth provider, or the callback page was added or renamed without updating the provider. Fix:- For Despia flow: register
https://yourapp.com/native-callback - For web flow: register
https://yourapp.com/auth - URLs must match exactly, no trailing slash differences
/native-callback or /native-callback.html is a redirect URI like any other. If you switch from /native-callback to /native-callback.html (the SPA fix below), that is a different URL, so register https://yourapp.com/native-callback.html or every login fails with an invalid redirect URI error.
Hash tokens lost in SPA routing
Problem: Tokens disappear from the URL hash before your callback code runs. Common with implicit-flow providers that return#access_token=....
Cause: SPA routers (React Router, Vue Router, and similar) handle the route change and strip the # fragment before your component mounts and reads it.
Solution: For any SPA, use a static native-callback.html in your public/ folder as the callback. It loads directly with no router involved, so the hash survives. Treat this as the default for SPAs, not just a fallback.
Create /native-callback.html:
.html URL with your provider, see Redirect URI mismatch above, or you get an invalid redirect URI error.
Why this works: Static HTML loads directly, no router involved, hash preserved.
Complete implementation
1. Login page (handles both flows):Debug checklist
When OAuth isn’t working: On login page:- Check user agent:
console.log(navigator.userAgent) - Verify correct flow selected
- Verify the Despia branch uses
despia('oauth://?url=...'), neverwindow.location.hrefto the provider - Verify OAuth URL is valid
- Verify OAuth URL is encoded
- Check page loads:
console.log('Callback loaded') - Check tokens received:
console.log('Token:', !!access_token) - Check deeplink format:
myapp://oauth/auth?... - Verify
oauth/prefix present - Verify the callback path you use is registered with the provider
- Check URL:
console.log(window.location.href) - Check for code (web):
console.log('Code:', code) - Check for tokens (Despia):
console.log('Token:', access_token) - Verify tokens stored in localStorage
- Verify navigation to /dashboard happens
Remember
The login page determines the flow.- Check
navigator.userAgent.includes('despia') - If true → Despia flow with
despia('oauth://...')and/native-callback - If false → Web flow with standard redirect and
/auth
- In the Despia branch, always use
despia('oauth://?url=...') - A direct
window.location.hrefto the provider loads it inside the WebView, which Google and most providers block - Only exception: Apple Sign-In on iOS, which uses a direct redirect by design
oauth/ prefix is critical.
- Deeplink format:
yourappdeeplink://oauth/auth?tokens - Without
oauth/→ browser won’t close - With
oauth/→ browser closes and app receives tokens
- SPA routers strip the
#fragment before your code reads it - A static
public/native-callback.htmlbypasses the router and preserves the hash - Register the exact
.htmlURL with your provider
- Adding or renaming
/native-callbackor/native-callback.htmlmeans updating the provider - A path the provider does not know returns an invalid redirect URI error
- Check for
despia-iphoneordespia-ipad - Use direct redirect (no
oauth://prefix) - Native Apple dialog opens automatically
For support or questions, contact: support@despia.com