Skip to main content

Command Palette

Search for a command to run...

🔐 Solving the Appwrite OAuth Redirect URI Issue in Expo Standalone Builds

Published
4 min read
🔐 Solving the Appwrite OAuth Redirect URI Issue in Expo Standalone Builds
D

I am a software developer, passionate about technology and community building! with high interest in expertly prepared noodles by myself

What started as a simple login flow became a two-day mystery. Here’s how I finally cracked the code, and what you need to know to avoid the same mess.

🚀 The Setup: FxLens + Expo + Appwrite

I’m building FxLens, an AI-powered forex trading assistant built with React Native and Expo and using Appwrite for backend services, including OAuth login.

Since the app uses push notifications and in-app purchases, I transitioned to a standalone dev client via EAS Build, not Expo Go. My goal was to enable Google login via Appwrite OAuth inside the mobile app.

According to the Appwrite and Expo docs, this seemed simple enough. But after two days of vague errors and misdirection, I finally found the real fix; and I'm writing this so you don’t have to suffer through it like I did.

😩 The Problem

Everything looked fine on paper.

I had:

  • The Appwrite SDK set up

  • OAuth2 provider (Google) configured

  • My app’s scheme defined in app.json

  • A dev build installed via EAS

  • The correct bundleIdentifier added in Appwrite’s platform settings

Yet every time I attempted to log in, I got one of these:

❌ Error 400: Invalid redirect URL for OAuth success.
❌ project_invalid_success_url
❌ Missing redirect URL
❌ Register your new client as a new platform


🤔 What the Docs Say to Do

Appwrite’s official docs (and Expo examples) suggest using makeRedirectUri() to generate your redirect URI, like this:

const deepLink = new URL(makeRedirectUri({ preferLocalhost: true }));

if (!deepLink.hostname) {
  deepLink.hostname = 'localhost';
}

const scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'playground://'

const loginUrl = await account.createOAuth2Token(
  provider,
  `${deepLink}`,
  `${deepLink}`
);

I tried this… and every variation of it.
I even removed preferLocalhost entirely, like so:

const deepLink = makeRedirectUri();

Then tested with:

  • fxlens://auth-callback

  • localhost

  • exp://...

  • The internal-looking Appwrite redirect URL (.../callback/google/<projectId>)

Nothing worked.

🤯 The Realization

Eventually, I noticed something odd: Appwrite was not accepting any redirect URI that I passed; even valid-looking deep links.

After digging through community threads and reverse-engineering the Appwrite login behaviour, I discovered this:

For mobile apps, Appwrite expects the redirect URI to match a very specific format:

appwrite-callback-<projectId>://auth

This isn't documented. But Appwrite internally derives this scheme based on your project ID and platform registration (iOS or Android). It does not accept just any deep link you craft yourself.

That’s why every variation I passed, including perfectly valid Expo-generated URIs, was getting rejected.

✅ The Fix (Finally)

To fix the problem, here’s exactly what I did:

1. Kept my existing scheme in app.json

No changes needed, I left my scheme as-is (e.g., fxlens).

2. Built the app using EAS:

eas build --profile dev --platform ios

3. Set the redirect URI to the Appwrite-magic format:

const redirectUri = "appwrite-callback-<projectId>://auth";

const response = await account.createOAuth2Token(
  OAuthProvider.Google,
  redirectUri,
  redirectUri
);

const result = await WebBrowser.openAuthSessionAsync(
  response.toString(),
  redirectUri
);

Note: Replace with your actual Appwrite project ID. You can find it in your Appwrite dashboard. But do not guess the URI or try to craft it manually; use the exact format.

4. Parsed the returned credentials:

const url = new URL(result.url);
const secret = url.searchParams.get("secret");
const userId = url.searchParams.get("userId");

await account.createSession(userId!, secret!);

✅ And it finally worked.

No more 400 errors.
No more SSL errors.
No more “register your client” confusion.
Just a smooth redirect back into the app and a created session.

.

🧠 Lessons Learned

  • Appwrite does not accept arbitrary success/failure URLs for OAuth

  • Even though the SDK lets you pass anything, only specific internal formats are accepted

  • Appwrite internally maps the platform via your bundleIdentifier and project ID, not via the scheme you define in app.json

🔥 What Doesn’t Work (But Looks Like It Should)

I tried all of these, none worked:

  • makeRedirectUri({ preferLocalhost: true })

  • fxlens://auth-callback

  • localhost

  • exp://...

  • Leaving the redirect URI empty

  • Manually crafting the full Appwrite callback URL

✨ Conclusion

If you’re using Appwrite + OAuth in a mobile app built with Expo and EAS:

TLDR:

Keep your existing scheme in app.json

  • Pass appwrite-callback-<projectId>://auth as your success and failure redirect URI

  • Let Appwrite handle the rest

This approach finally solved the issue for me while building FxLens, and I hope it saves someone else the same pain.

If you’ve been stuck with vague OAuth redirect errors and broken flows, this is the fix you’re looking for.