sdk-for-react-native icon indicating copy to clipboard operation
sdk-for-react-native copied to clipboard

Invalid success param when using OAuth2 login

Open schoolofai opened this issue 1 year ago • 9 comments

hi i am having issues with using google oauth for login - I am using react native sdk - it all works when on expo go , but when i installed it to testfight it stops working - and when trying to login with google - in get invalid sucess param should be one of - localhost .... etc . here is my sign in code -

    let redirectUri = makeRedirectUri({ preferLocalhost: true });
    console.log("Redirect URI:", redirectUri);

    const url = await account.createOAuth2Token(
      "google",
      redirectUri,
      redirectUri,
      [
        "profile",
        "email",
        "https://www.googleapis.com/auth/youtube",
        "https://www.googleapis.com/auth/youtube.force-ssl",
        "https://www.googleapis.com/auth/youtube.upload",
        "https://www.googleapis.com/auth/youtubepartner",
      ]
    );
    console.log("URL:", url);
    if (!url) {
      throw new Error("Failed to create OAuth2 session");
    }

    const result = await openAuthSessionAsync(url.href, redirectUri);
    console.log("Auth session result:", result);

    if (result.type === "success") {
      if ("url" in result) {
        const resultUrl = new URL(result.url);
        const secret = resultUrl.searchParams.get("secret");
        const userId = resultUrl.searchParams.get("userId");
        if (!secret || !userId) return;
        await account.createSession(userId, secret);
        const user = await account.get().catch((e) => {
          console.warn(e);
          return null;
        });
        console.log("user:", user);

see the screen shot for google cloud credentials ImageImage

schoolofai avatar Oct 10 '24 09:10 schoolofai

@schoolofai, thanks for creating this issue! 🙏🏼 Could you please share exactly what the redirectUri is when running via TestFlight? It's likely the hostname in the URL hasn't been registered in your Appwrite project yet.

stnguyen90 avatar Oct 28 '24 22:10 stnguyen90

So, at the moment, Expo will create deep links like:

  • Development and production builds: <scheme>://path - uses the optional scheme property if provided, and otherwise uses the first scheme defined by your app config
  • Web (dev): https://localhost:19006/path
  • Web (prod): https://myapp.com/path
  • Expo Go (dev): exp://128.0.0.1:8081/--/path

Appwrite will try to validate the host part of the URL against the allowed web platforms to protect against open redirect attacks so you'll get a 400 error like:

Invalid success param: URL host must be one of: localhost, cloud.appwrite.io, appwrite.io

It's impossible to change RN to include a hostname or you may run into a path not found error when redirect back into the RN app.

We'll have to discuss internally about how to handle react native.

stnguyen90 avatar Jan 18 '25 22:01 stnguyen90

https://github.com/appwrite/appwrite/pull/9262 was merged but then reverted due to issues during QA. We'll need to fix the issues with another PR.

stnguyen90 avatar Feb 12 '25 22:02 stnguyen90

same issue here general_argument_invalid working fine in ios simulation and expo qr code run issue in android emulator export async function login() { try { // need to generate redirect uri for oauth response (once go to google it has to move back to application again (can use expo module foe handling deep links)) const redirectUri = Linking.createURL("/");

// padding redirect url and auth provider
const response = await account.createOAuth2Token(
  OAuthProvider.Google,
  redirectUri
);
if (!response) throw new Error("Failed to Login");

const browserResult = await openAuthSessionAsync(
  response.toString(),
  redirectUri
);
if (browserResult.type !== "success") throw new Error("Failed to login");
const url = new URL(browserResult.url);

Nizam-shan avatar Mar 27 '25 13:03 Nizam-shan

I have the same problem, here is my current implementation


import { getQueryParams } from "expo-auth-session/build/QueryParams";
import { openAuthSessionAsync } from "expo-web-browser";

----

const createSessionFromUrl = async (url: string) => {
  try {
    const { params, errorCode } = getQueryParams(url);

    if (errorCode) throw new Error(errorCode);
    const { secret, userId } = params;

    if (!secret || !userId) return;

    return await account.createSession(userId, secret);
  } catch (error) {
    console.error("Error creating session from URL:", error);
    throw error;
  }
};

  signWithOAuth: async (provider: OAuthProvider) => {
    const url = await account.createOAuth2Session(
      provider,
      "myCoolAppwriteApp://(authenticated)",
      "myCoolAppwriteApp://auth",
      ["openid", "profile"]
    );

    if (!url) throw new Error("Failed to create OAuth2 session");

    const res = await openAuthSessionAsync(
      url.toString(),
      "myCoolAppwriteApp://home"
    );

    if (res.type === "success") {
      const { url } = res;

      if (!url) throw new Error("Failed to create session from URL");

      await createSessionFromUrl(url);
    }
  },

mnkyjs avatar Mar 28 '25 11:03 mnkyjs

Did u find any solution Is this our implementation problem or appwrite issue in local ecpo its working fine because its localhost the deep link concept is not working

On Fri, 28 Mar, 2025, 5:00 pm Dennis Hundertmark, @.***> wrote:

I have the same problem, here is my current implementation

import { getQueryParams } from "expo-auth-session/build/QueryParams";import { openAuthSessionAsync } from "expo-web-browser";

const createSessionFromUrl = async (url: string) => { try { const { params, errorCode } = getQueryParams(url);

if (errorCode) throw new Error(errorCode);
const { secret, userId } = params;

if (!secret || !userId) return;

return await account.createSession(userId, secret);

} catch (error) { console.error("Error creating session from URL:", error); throw error; }};

signWithOAuth: async (provider: OAuthProvider) => { const url = await account.createOAuth2Session( provider, "myCoolAppwriteApp://(authenticated)", "myCoolAppwriteApp://auth", ["openid", "profile"] );

if (!url) throw new Error("Failed to create OAuth2 session");

const res = await openAuthSessionAsync(
  url.toString(),
  "myCoolAppwriteApp://home"
);

if (res.type === "success") {
  const { url } = res;

  if (!url) throw new Error("Failed to create session from URL");

  await createSessionFromUrl(url);
}

},

— Reply to this email directly, view it on GitHub https://github.com/appwrite/sdk-for-react-native/issues/34#issuecomment-2761101935, or unsubscribe https://github.com/notifications/unsubscribe-auth/A2VPJ3C4HUUREW242XTFS5L2WUQDDAVCNFSM6AAAAABQYN6EECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONRRGEYDCOJTGU . You are receiving this because you commented.Message ID: @.***> [image: mnkyjs]mnkyjs left a comment (appwrite/sdk-for-react-native#34) https://github.com/appwrite/sdk-for-react-native/issues/34#issuecomment-2761101935

I have the same problem, here is my current implementation

import { getQueryParams } from "expo-auth-session/build/QueryParams";import { openAuthSessionAsync } from "expo-web-browser";

const createSessionFromUrl = async (url: string) => { try { const { params, errorCode } = getQueryParams(url);

if (errorCode) throw new Error(errorCode);
const { secret, userId } = params;

if (!secret || !userId) return;

return await account.createSession(userId, secret);

} catch (error) { console.error("Error creating session from URL:", error); throw error; }};

signWithOAuth: async (provider: OAuthProvider) => { const url = await account.createOAuth2Session( provider, "myCoolAppwriteApp://(authenticated)", "myCoolAppwriteApp://auth", ["openid", "profile"] );

if (!url) throw new Error("Failed to create OAuth2 session");

const res = await openAuthSessionAsync(
  url.toString(),
  "myCoolAppwriteApp://home"
);

if (res.type === "success") {
  const { url } = res;

  if (!url) throw new Error("Failed to create session from URL");

  await createSessionFromUrl(url);
}

},

— Reply to this email directly, view it on GitHub https://github.com/appwrite/sdk-for-react-native/issues/34#issuecomment-2761101935, or unsubscribe https://github.com/notifications/unsubscribe-auth/A2VPJ3C4HUUREW242XTFS5L2WUQDDAVCNFSM6AAAAABQYN6EECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONRRGEYDCOJTGU . You are receiving this because you commented.Message ID: @.***>

Nizam-shan avatar Mar 29 '25 00:03 Nizam-shan

I having same issue Error 400

Invalid 'success' param: URL host must be one of: localhost, appwrite.io, *.appwrite.io, fra.cloud.appwrite.io, *

Type

general_argument_invalid

subhodeep2005s avatar Apr 25 '25 11:04 subhodeep2005s

appwrite/appwrite#9262 was merged but then reverted due to issues during QA. We'll need to fix the issues with another PR.

@stnguyen90 so what's the solution now?

ammarfaris avatar May 16 '25 00:05 ammarfaris

Has anyone gotten a solution yet?

solomonojox avatar May 20 '25 00:05 solomonojox

...
export async function login() {
  try {
  
    const redirectUri = Linking.createURL("/");

    console.log("Redirect URI:", redirectUri);

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

When launching the project via expo, everything works and is displayed in the console: Redirect URI: exp://192.168.100.8:8082/--/

And with the native build for Android, error 400 and console output: Redirect URI: com.varn0r.findroute:///

Maybe this will help you solve the problem somehow.🙏

VARN0R avatar May 25 '25 17:05 VARN0R

I found this article quiet helpful and was able to enable OIDC Provider (Keycloak) and get everything to work.

https://bishwajeet-parhi.medium.com/i-built-an-auth-template-powered-by-react-native-and-appwrite-4a0b7ee90ba6

mnkyjs avatar May 26 '25 13:05 mnkyjs

I also ran into this issue in my project. I have been on it for a couple of days and here are my findings:

  1. createOAuth2Session method will not work for android, iOS or emulators. This is because the result does not persist after redirection. The solution is to use createOAuth2Token.

However, createOAuth2Session works fine on the web. And createOAuth2Token doesn't seem to work on the web for some reason.

  1. Whether you use makeRedirectUri({ preferLocalhost: true }); or deep-linking Linking.createURL("/"), authentication would still fail because the redirectUri used is not supported.You might even run into this error countlessly: Invalid success param: URL host must be one of: localhost, appwrite.io, *.appwrite.io, cloud.appwrite.io.

The only solution I came across was to create a new platform for the same project. Web in this case. As you can't add urls like exp://192.168.x.x:8081/--/auth to Google Console. I used JavaScript as the project type.

This will provide you a url - usually http://localhost:5173. This is what you'd add to your Google Console's redirect URI list. Since both platform's share the same project (which is used in creating the account object), during redirection, the redirectUri used under the hood will be the one set in Google Console - http://localhost:5173 in this case.

NB: Ensure to set hostname as * (for testing purposes only).

This solution works for me on Android, iOS, web, and my Android emulator.

Here is the solution that works for me:

export async function login() {
  try {
    // Creates a success redirect url after authentication is successful
    const redirectUri = Linking.createURL("/");

    console.log("Platform:", Platform.OS);
    console.log("Redirect URI:", redirectUri);

    if (Platform.OS === "web") {
      // Use createOAuth2Session for web
      const response = account.createOAuth2Session(
        OAuthProvider.Google,
        redirectUri,
        redirectUri
      );

      if (!response) throw new Error("Failed to create OAuth2 session");

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

      if (browserResult.type !== "success") {
        throw new Error("OAuth authentication was cancelled or failed");
      }

      // For web, session is automatically created
      const currentSession = await account.getSession("current");
      if (!currentSession) {
        throw new Error("No session found after OAuth authentication");
      }
      return currentSession;
    } else {
      // Use createOAuth2Token for android and ios to ensure cookies
      // are shared between app and in-app browser after redirection
      const response = account.createOAuth2Token(
        OAuthProvider.Google,
        redirectUri
      );
      if (!response) throw new Error("Create OAuth2 token failed");

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

      if (browserResult.type !== "success")
        throw new Error("Create OAuth2 token failed");

      console.log("Browser Result: ", browserResult);

      const url = new URL(browserResult.url);
      const secret = url.searchParams.get("secret")?.toString();
      const userId = url.searchParams.get("userId")?.toString();
      if (!secret || !userId) throw new Error("Create OAuth2 token failed");

      // Create session manually using userId and secret
      await account.createSession(userId, secret);

      // Return the newly created session
      const currentSession = await account.getSession("current");
      if (!currentSession) {
        throw new Error("Failed to create session after OAuth authentication");
      }
      return currentSession;
    }
  } catch (error) {
    console.error("Login error:", error);
    return null;
  }
}

I hope this solution will be helpful to whoever comes across it. I also believe that it can be improved upon.

peterxavier01 avatar May 31 '25 15:05 peterxavier01

We've made some improvements to the OAuth2 flow for React Native. Moving forward, please set the URL scheme to appwrite-callback-<PROJECT_ID> in your app.json file.

{
  "expo": {
    "scheme": "appwrite-callback-<PROJECT_ID>"
  }
}

Then, create a deep link, pass it to account.createOAuth2Token() method to create the login URL, open the URL in a browser, listen for the redirect, and finally create a session with the secret.

import { Client, Account, OAuthProvider } from "appwrite";
import { makeRedirectUri } from 'expo-auth-session'
import * as WebBrowser from 'expo-web-browser';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                          // Your project ID

const account = new Account(client);

// Create deep link that works across Expo environments
// Ensure localhost is used for the hostname to validation error for success/failure URLs
const deepLink = new URL(makeRedirectUri({ preferLocalhost: true }));
const scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'appwrite-callback-<PROJECT_ID>://'

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

// Open loginUrl and listen for the scheme redirect
const result = await WebBrowser.openAuthSessionAsync(`${loginUrl}`, scheme);

// Extract credentials from OAuth redirect URL
const url = new URL(result.url);
const secret = url.searchParams.get('secret');
const userId = url.searchParams.get('userId');

// Create session with OAuth credentials
await account.createSession(userId, secret);
// Redirect as needed

stnguyen90 avatar Jul 11 '25 20:07 stnguyen90