Custom OAuth2 Provider with Expo and Universal Links
I’ve implemented a custom OAuth2 OpenID provider (Vipps Login) using Better Auth and Expo. The flow isn’t pure browser-based, during login, the user is redirected from my app to the Vipps app for confirmation and then back to the Better Auth callback (/auth/oauth2/callback/vipps).
The flow works fine when I let Vipps redirect back to Safari:
1. App →
2. Vipps redirects to
3. That endpoint validates state, redirects to
1. App →
Browser.openAuthSessionAsync() → Vipps page → user opens Vipps app → approves login2. Vipps redirects to
https://api.mydomain.com/auth/oauth2/callback/vipps?state=xxx&code=xxx3. That endpoint validates state, redirects to
myapp:///?cookie=xxx, Safari shows the “Open in App?” alert → app is opened.
In the final step, my app receives the cookie query param and sets it like this:
This works fine, but the only downside is that Safari briefly opens Safari and the OS‑style "Open App?" alert.
I tried to improve that using Universal Links so Vipps could redirect directly back to my app instead of via Safari. That part works UX‑wise, but then I get a state_mismatch from Better Auth during this callback:
After inspecting Better Auth’s code, I see it expects a signed state cookie to be present to compare against, but in the universal‑link flow, that cookie isn’t set (since the app, not Safari, calls the callback).
Any ideas on how to solve this, while also keeping it secure? Would like to avoid opening Safari for better UX...0 Replies