Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Usage with react native #569

Closed
1 of 5 tasks
mjewell opened this issue Aug 11, 2020 · 30 comments
Closed
1 of 5 tasks

Usage with react native #569

mjewell opened this issue Aug 11, 2020 · 30 comments
Labels
documentation Relates to documentation help needed The maintainer needs help due to time constraint/missing knowledge question Ask how to do something or how something works stale Did not receive any activity for 60 days

Comments

@mjewell
Copy link

mjewell commented Aug 11, 2020

Is it possible to use next-auth with a react native app? I originally started out using next.js for everything but wanted to switch to a native app with the REST API side in next.js/vercel.

The FAQ says:

It is not intended to be used in native applications on desktop or mobile applications, which typically use public clients (e.g. with client / secrets embedded in the application).

But it looks like apps are able to also use web browser based OAuth flows. Specifically I am trying to use Expo AuthSession.

I have managed to get the OAuth code using the example here. Logging in with google returns an object like this:

{
  "authuser": "0",
  "code": "<random code string>",
  "prompt": "none",
  "scope": "profile openid https://www.googleapis.com/auth/userinfo.profile",
  "state": "<random string>",
}

So I was hoping I could then manually make a request to GET /api/auth/callback/google the same way the OAuth redirect would. It looks like the callback endpoint sets the state to the hashed CSRF token so I tried to make the request like this:

fetch("http://localhost:3000/api/auth/csrf")
  .then((r) => r.json())
  .then((r) => {
    return Crypto.digestStringAsync(
      Crypto.CryptoDigestAlgorithm.SHA256,
      r.csrfToken
    );
  })
  .then((csrf) => {
    // response.params is the object from above
    const params = { ...response.params, state: csrf };
    return fetch(
      `http://localhost:3000/api/auth/callback/google?${serialize(
        params
      )}`
    );
  });

Which produced a request like this:

http://localhost:3000/api/auth/callback/google?code=<random code string>&scope=profile%20openid%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none&state=<hashed csrf token>

But I get the following errors:

[next-auth][error][OAUTH_GET_ACCESS_TOKEN_ERROR] [
  {
    statusCode: 400,
    data: '{\n' +
      '  "error": "invalid_grant",\n' +
      '  "error_description": "Missing code verifier."\n' +
      '}'
  },
  undefined,
  undefined
]
https://next-auth.js.org/errors#oauth_get_access_token_error
[next-auth][error][OAUTH_GET_ACCESS_TOKEN_ERROR] [
  {
    statusCode: 400,
    data: '{\n' +
      '  "error": "invalid_grant",\n' +
      '  "error_description": "Missing code verifier."\n' +
      '}'
  },
  undefined,
  'google',
  '<random code string>'
]
https://next-auth.js.org/errors#oauth_get_access_token_error
[next-auth][error][OAUTH_GET_PROFILE_ERROR] [
  {
    statusCode: 401,
    data: '{\n' +
      '  "error": {\n' +
      '    "code": 401,\n' +
      '    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",\n' +
      '    "status": "UNAUTHENTICATED"\n' +
      '  }\n' +
      '}\n'
  }
]

I don't have a lot of experience with this so I can't really tell if this is a reasonable / secure approach that is close to working or if I'm just going down completely the wrong path. I'd love to hear your thoughts on whether this is something next-auth can support or if this is out of scope for the project.

Documentation feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.

  • Found the documentation helpful
  • Found documentation but was incomplete
  • Could not find relevant documentation
  • Found the example project helpful
  • Did not find the example project helpful
@mjewell mjewell added the question Ask how to do something or how something works label Aug 11, 2020
@fishactual
Copy link

Keen to understand if this is possible right now as well. We are evaluating next-auth and we're planning on writing a React Native app and this will be a major factor in our decision making.

@iaincollins iaincollins added the help needed The maintainer needs help due to time constraint/missing knowledge label Aug 12, 2020
@iaincollins
Copy link
Member

Hi there, thank you for the great question!

So the short answer right now is I guess no, at least not out of the box. :-(

The longer answer is yes it's I think it's possible … but we don't have an example of how to do that - and probably things we could do to make this easier, such as not requiring the CSRF Token if it's a native app.

If I understand what you are doing correctly, then technically that seems valid at least for Google according to the docs. It's having an error getting the access token which suggests the provider is not configured correctly for the credentials it's using.

Maybe additional or different options on the provider object need to be configured in this scenario?
https://next-auth.js.org/configuration/providers#oauth-provider-options

Not all the options are documented I think, you might find some more poking around in the providers:
https://github.com/nextauthjs/next-auth/tree/main/src/providers

I'd love to dive into this but don't think I can provide meaningful help or a better answer right now, but I'm happy to leave this open until we do have a better answer. I'm also happy to take feature requests to make using it with React Native easier.

One thing to maybe check is how the OAuth credentials are configured in Google - there are often different ways of configuring an OAuth client and I'm not sure what the relevant options might be in this case.

FWIW, IMO the very best way to support OAuth sign in with an app, if you can do it, it is to run a website that persists the user data and lets people sign in. This has the downside of requiring a web site hosted somewhere but the upside of being more secure and reducing the amount of code in your app allowing you to persist user data across platforms. The only caveat with this approach is that you want to make sure that the callback to your app cannot be intercepted by another app. (Happy to go into that in more detail if anyone is interested).

@mjewell
Copy link
Author

mjewell commented Aug 12, 2020

Awesome, thanks for the detailed response and the links. I'll keep digging into this and update here with whatever I find.

I'm definitely interested in hearing more about that last option. Keeping the website running is not a problem for me.

@mjewell
Copy link
Author

mjewell commented Aug 12, 2020

Ok so a few updates...

  1. It turns out I am using next-auth@2.2.0 so the CSRF checks were actually not even happening. I'm going to upgrade my app to 3.1 before I do much more with this. If that causes the CSRF checking to be a problem it looks like you can disable the CSRF checks by adding state: false to the provider. Probably not the right solution long term...
  2. The expo authentication was using PKCE which is why I was getting the missing code verifier issue. Disabling that by adding usePKCE: false to useAuthRequest in expo got me past that error. Again, long term probably not a great choice, but at least for now it got me one step further.
  3. The expo guide ends up generating a redirect uri of https://auth.expo.io/@<username>/<appname> (https://docs.expo.io/versions/latest/sdk/auth-session/#what-authexpoio-does-for-you) so I started getting a redirect_uri_mismatch error. Updating my google provider config to point to that seemed to solve the problem:
    Providers.Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      params: {
        grant_type: "authorization_code",
        redirect_uri: "https://auth.expo.io/@<username>/<appname>",
      },
    }),

Doing that, everything seems to work!

Here's what I ended up with in expo:

  const discovery = useAutoDiscovery("https://accounts.google.com");

  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: "<google client id>.apps.googleusercontent.com",
      redirectUri: makeRedirectUri({
        native: "<google client id>:/oauthredirect",
        useProxy,
      }),
      usePKCE: false,
      scopes: ["openid", "profile"],
      prompt: Prompt.SelectAccount,
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === "success") {
      fetch(
        `http://localhost:3000/api/auth/callback/google?${serialize(response.params)}`
      ).then(() => {
        fetch("http://localhost:3000/api/my_authenticated_route")
          .then(r => r.json())
          .then(console.log)
          .catch(console.log);
      });
    }
  }, [response]);

And with that I see the response body of the my_authenticated_route API. Lots of shortcuts taken so far, but at least it seems like things can work.

@iaincollins
Copy link
Member

Oh congratulations - that's great to hear, thanks for sharing!

I wonder if we can turn something like this into a tutorial, so it's much easier for the next person. I appreciate this must have been quite a bit of digging!

FWIW using state: false is actually fine! It doesn't really come into play with NextAuth.js because it does other things that make it redundant as a security check - like the redirect callback and ignoring the redirect URL returned by the remote service and because it is a server side flow rather than a client side flow

However, the option is still supported and enabled by default for RFC compliance; there is some redundancy in the spec because of all the different flows (PKCE is similar, but is actually relevant in this case).

Disabling PKCE isn't ideal in the longer term, but I think is something we can help with! It is basically the same caveat with the other approach I was talking about above. I'll have a think and try and remember where I last got to with that…

@mjewell
Copy link
Author

mjewell commented Aug 12, 2020

Upgrade to 3.1 was super smooth. Added back the CSRF fetching code from the original post and next-auth seems happy with that (and passing a different value for the state does cause the expected Invalid state returned from oAuth provider error).

One big drawback to this approach is that you can only have either the web log in or the app log in working. If you try to log in to the web version with the above change to the provider.params.redirect_uri you get a redirect_uri_mismatch error because the sign in route doesn't respect that parameter the way the callback one does (https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/signin/oauth.js#L11 vs https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/callback.js#L210). If I update the sign in code to respect provider.params.redirect_uri (redirect_uri: provider.params.redirect_uri || provider.callbackUrl) then I get an error after logging in through the web ui which I assume is because expo is trying to redirect me back to the app but that fails.

image

It seems like this could be fixed if the GET /api/auth/callback/:provider route could take an optional redirect_uri query parameter that the app could pass in to override the default, which seems fine to me, but I don't know if that exposes other security risks.

I am going to keep going working on the app in this state and see if I encounter any other issues since this is still a pretty primitive test, then I'll come back and investigate re-adding PKCE. If you have any suggestions about how that might be solved that would be great.

Once I get this fully working I'll try to piece together all the final instructions into a single comment and we can put that into the documentation if everything looks good.

@mjewell
Copy link
Author

mjewell commented Aug 12, 2020

Ok so looks like there are a few more issues using some of the client API in react native. Specifically I am trying to use useSession, but there may be issues with other methods too.

  1. In react native it seems like window is defined, but window.addEventListener is not. Importing next-auth/client throws an error that I was able to fix by setting window.addEventListener = function () {}; before that import. This stops the import from failing but obviously means you lose all the functionality that is based on that.
  2. next-auth assumes that in a client environment you want to use relative URLs for API calls, but we will need to use absolute URLs.
  3. All the client methods that change the session (signIn/signOut/others?) redirect you away and trigger full page refreshes, but those redirects are no-ops in react native. This means that you need to manually refresh the app after you log in / log out to see the updated UI. I tried to trigger re-renders in different parts of my app when the session changed but I couldn't get it to update.
  4. localStorage is also not defined so anything that depends on that will not work either, eg: https://github.com/nextauthjs/next-auth/blob/main/src/client/index.js#L319

@mjewell
Copy link
Author

mjewell commented Aug 15, 2020

Quick update regarding

One big drawback to this approach is that you can only have either the web log in or the app log in working

Originally my plan was to have multiple instances of the provider I was using with different configuration settings and different ids, but I ran into issues where logging in with the same account across platforms created separate users in next-auth. I was able to get around this by adding a custom header to each client (in my case by patching fetch):

const originalFetch = fetch;
fetch = (url, options = {}) => {
  return originalFetch(url, {
    ...options,
    headers: {
      ...options.headers,
      "next-auth-platform": 'ios',
    },
  });
};

And then in the next-auth configuration I choose the provider based on that header:

const getOptions = platform => ({
  providers: [
    platform === "ios"
      ? Providers.Google({
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
          params: {
            grant_type: "authorization_code",
            redirect_uri: "https://auth.expo.io/@<username>/<appname>",
          },
        })
      : Providers.Google({
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        })
  ],
  /* database, callbacks, whatever */
});

export default (req: NextApiRequest, res: NextApiResponse) => {
  const options = getOptions(req.headers["next-auth-platform"]);
  return NextAuth(req, res, options);
};

@LoriKarikari LoriKarikari added the documentation Relates to documentation label Aug 29, 2020
@stale
Copy link

stale bot commented Dec 5, 2020

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep ot open. Thanks!

@stale stale bot added the wontfix This will not be worked on label Dec 5, 2020
@balazsorban44 balazsorban44 added stale Did not receive any activity for 60 days and removed wontfix This will not be worked on labels Dec 5, 2020
@stale
Copy link

stale bot commented Dec 12, 2020

Hi there! It looks like this issue hasn't had any activity for a while. To keep things tidy, I am going to close this issue for now. If you think your issue is still relevant, just leave a comment and I will reopen it. (Read more at #912) Thanks!

@stale stale bot closed this as completed Dec 12, 2020
@Xodarap
Copy link
Contributor

Xodarap commented Dec 26, 2020

@mjewell Did you ever get this working? I want to do something similar

@mjewell
Copy link
Author

mjewell commented Dec 26, 2020

Yeah, I did! There's quite a bit of code and I'm using expo so there's some stuff specific to that in here too, but if you're not hopefully you can piece things together from here... Here's how:

First, we need to set some stuff up at app initialization time.

// initializers/hackNextAuth.js
import { DeviceEventEmitter } from "react-native";

// next-auth uses addEventListener, define that so focus/blur trigger next auth listeners
window.addEventListener = (...args) => DeviceEventEmitter.addListener(...args);

let codeVerifier = null;

export function setCodeVerifierHeader(verifier) {
  codeVerifier = verifier;
}

console.log(`Using api at: ${process.env.API_URL}`);
let originalFetch = fetch;
// eslint-disable-next-line no-global-assign
fetch = (url, options = {}) => {
  // turn relative urls into absolute urls on the API path
  const modifiedUrl =
    url.startsWith("http://") || url.startsWith("https://")
      ? url
      : `${process.env.API_URL}/${url.startsWith("/") ? url.slice(1) : url}`;

  if (codeVerifier) {
    options.headers = { "my-app-code-verifier": codeVerifier, ...options.headers };
  }

  return originalFetch(modifiedUrl, options);
};
// initializers/addPlatformHeaders.js
import Constants from "expo-constants";
import { Platform } from "react-native";

const originalFetch = fetch;
// eslint-disable-next-line no-global-assign
fetch = (url, options = {}) => {
  return originalFetch(url, {
    ...options,
    headers: {
      ...options.headers,
      "my-app-ownership": Constants.appOwnership,
      "my-app-platform": Platform.OS,
    },
  });
};

Make sure you have an environment variable API_URL set up to point to your back end. For local dev this is something like API_URL=http://192.168.1.1:3000.

Then import these files before you do anything else (at the top of App.js):

// App.js
import "./initializers/addPlatformHeaders";
import "./initializers/hackNextAuth";
// other imports

// other stuff

Add log in buttons to your app. For google this looks something like this:

// utils/exchangeToken.ts
import { setCodeVerifierHeader } from "@/initializers/hackNextAuth";
import { useAuthRequest } from "expo-auth-session";
import * as Crypto from "expo-crypto";
import { getCsrfToken } from "next-auth/client";
import qs from "qs";
import { DeviceEventEmitter } from "react-native";

export default async function exchangeToken(
  providerId: string,
  request: ReturnType<typeof useAuthRequest>[0],
  response: ReturnType<typeof useAuthRequest>[1]
) {
  setCodeVerifierHeader(request?.codeVerifier);
  const csrf = await getCsrfToken();
  const state = await Crypto.digestStringAsync(
    Crypto.CryptoDigestAlgorithm.SHA256,
    csrf
  );
  const params = { ...(response as any).params, state };
  try {
    await fetch(`/api/auth/callback/${providerId}?${qs.stringify(params)}`, {
      redirect: "error",
    });
  } catch (e) {
    // Redirects can cause errors that we can ignore
    // TODO: distinguish these from failed logins and show an error
    console.log(e);
  } finally {
    setCodeVerifierHeader(null);
  }
  DeviceEventEmitter.emit("focus");
}
// google.tsx
import { Button } from "@/components/Button";
import {
  Prompt,
  makeRedirectUri,
  useAuthRequest,
  useAutoDiscovery,
} from "expo-auth-session";
import Constants from "expo-constants";
import React from "react";
import { Platform } from "react-native";
import exchangeToken from "./utils/exchangeToken";

function reverseDomain(domain: string) {
  return domain.split(".").reverse().join(".");
}

const clientId =
  Constants.appOwnership === "expo"
    ? process.env.GOOGLE_CLIENT_ID!
    : Platform.select({
        ios: process.env.GOOGLE_IOS_CLIENT_ID!,
        android: process.env.GOOGLE_ANDROID_CLIENT_ID!,
        default: process.env.GOOGLE_CLIENT_ID!,
      });

const useProxy = Constants.appOwnership === "expo";

const native = Platform.select({
  ios: `${reverseDomain(process.env.GOOGLE_IOS_CLIENT_ID!)}:/oauthredirect`,
  android: `com.myapp.app:/oauthredirect`,
});

export default function GoogleLogIn() {
  const discovery = useAutoDiscovery("https://accounts.google.com");

  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId,
      redirectUri: makeRedirectUri({
        native,
        useProxy,
      }),
      scopes: ["openid", "profile", "email"],

      // Optionally should the user be prompted to select or switch accounts
      prompt: Prompt.SelectAccount,
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === "success") {
      exchangeToken("google", request, response);
    }
  }, [response]);

  return (
    <Button
      disabled={!request}
      onPress={() => {
        promptAsync({ useProxy });
      }}
    >
      Login with Google
    </Button>
  );
}

Remember to emit focus events whenever you sign in/out to make sure next-auth re-requests the token, e.g.:

async function signOutAndRefresh() {
  await signOut();
  DeviceEventEmitter.emit("focus");
}

That should be all for the app side of things. Then on the api side you want something like this:

// [...nextauth].ts
import { NextApiRequest, NextApiResponse } from "next";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";

function reverseDomain(domain: string) {
  return domain.split(".").reverse().join(".");
}

const getOptions = (
  ownership: string | string[] | undefined,
  platform: string | string[] | undefined,
  codeVerifier: string | string[] | undefined
) => ({
  providers: [
    ownership === "expo"
      ? Providers.Google({
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
          params: {
            grant_type: "authorization_code",
            redirect_uri: "https://auth.expo.io/@username/app-name",
          },
        })
      : platform === "ios"
      ? Providers.Google({
          clientId: process.env.GOOGLE_IOS_CLIENT_ID,
          params: {
            grant_type: "authorization_code",
            redirect_uri: `${reverseDomain(
              process.env.GOOGLE_IOS_CLIENT_ID!
            )}:/oauthredirect`,
            code_verifier: codeVerifier,
          },
        })
      : platform === "android"
      ? Providers.Google({
          clientId: process.env.GOOGLE_ANDROID_CLIENT_ID,
          params: {
            grant_type: "authorization_code",
            redirect_uri: "com.myapp.app:/oauthredirect",
            code_verifier: codeVerifier,
          },
        })
      : Providers.Google({
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        }),
  ],
});

const handler = (req: NextApiRequest, res: NextApiResponse) => {
  const options = getOptions(
    req.headers["my-app-ownership"],
    req.headers["my-app-platform"],
    req.headers["my-app-code-verifier"]
  );
  return NextAuth(req, res, options);
};

export default handler;

That should allow you to log in through the web, with expo, or in native apps all based on what the headers say. If you don't need all that you can remove the corresponding providers.

One final weird thing is that on android if you open the app, press back to close it, then reopen the app, you'll get stuck with no session in a loading state. It looks like this will fix that for you facebook/react-native#13775 (comment). There might be a better solution for that, I'm not really sure what other implications it has.

Hopefully that's helpful. I might have forgotten some stuff I've done along the way (it has been a while and I didn't keep great docs about what I chose to do). Let me know if anything doesn't work and I can look into it.

@Xodarap
Copy link
Contributor

Xodarap commented Dec 30, 2020

Thanks @mjewell ! This is extraordinarily helpful.

Documenting my progress so far:

  1. I couldn't get the CSRF function to work so I just ended up doing
const csrf = await fetch( `${domain}/api/auth/csrf`).then(async response =>
  (await response.json() )['csrfToken'])
  1. This still wasn't working for some reason so I just set state: false for now as you suggested above.

I'm still getting the following error; will have to look into it tomorrow, but if you have any thoughts about what might be causing this I would love to hear them

[oauth_get_access_token_error] [
  {
    statusCode: 400,
    data: '{\n  "error": "invalid_grant",\n  "error_description": "Bad Request"\n}'
  },
  undefined,
  undefined
] 

@mjewell
Copy link
Author

mjewell commented Dec 30, 2020

Are you using version next-auth@3.1? Also, what provider are you using?

@Xodarap
Copy link
Contributor

Xodarap commented Dec 31, 2020

Yeah, I'm on 3.1. Using Google.

@bratsos
Copy link

bratsos commented Jan 3, 2021

I'm trying to do something similar - it would be great if we could get a full-blown example in the docs after we pin down the exact steps needed.

@mjewell

  1. Does those changes allow you to use useSession like you would in a web app?
  2. Do you also use "traditional" Nextjs for a web app or just for the backend/endpoints?

@mjewell
Copy link
Author

mjewell commented Jan 4, 2021

Did you set up all those environment variables? For me they look like this on the backend:

GOOGLE_CLIENT_ID=<random_id>.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=<random_id>
GOOGLE_ANDROID_CLIENT_ID=<random_id>.apps.googleusercontent.com
GOOGLE_IOS_CLIENT_ID=<random_id>.apps.googleusercontent.com

And you need the three client ids defined in your app environment variables too. Make sure all those URLs look like what you would expect.

I haven't hit the invalid_grant issue, but I did notice I was intermittently getting errors (probably about 20% of the time) because the CSRF token was different so the code didn't match. Since then I've removed the state param from the request. Here's what my new exchangeToken.ts looks like:

import { setCodeVerifierHeader } from "@/initializers/hackNextAuth";
import { useAuthRequest } from "expo-auth-session";
// import * as Crypto from "expo-crypto";
// import { getCsrfToken } from "next-auth/client";
import qs from "qs";
import { DeviceEventEmitter } from "react-native";
import * as Sentry from "sentry-expo";

export default async function exchangeToken(
  providerId: string,
  request: ReturnType<typeof useAuthRequest>[0],
  response: ReturnType<typeof useAuthRequest>[1]
) {
  setCodeVerifierHeader(request?.codeVerifier);
  // CSRF was inconsistently matching causing intermittent failures, can be ignored
  // https://github.com/nextauthjs/next-auth/issues/569#issuecomment-672968577
  // const csrf = await getCsrfToken();
  // const state = await Crypto.digestStringAsync(
  //   Crypto.CryptoDigestAlgorithm.SHA256,
  //   csrf
  // );
  // const params = { ...(response as any).params, state };
  const params = { ...(response as any).params };
  try {
    const response = await fetch(
      `/api/auth/callback/${providerId}?${qs.stringify(params)}`
    );

    if (response.url.includes("/error")) {
      throw new Error(`Authentication Failed: ${response.url}`);
    }
  } catch (e) {
    // Using localhost, we will hit Network Request Failed for this request even when its
    // successful on android device/simulator and ios device, but not ios simulator
    // TODO: this will also hide legitimate failures locally, find a way to distinguish
    const isLocalhostIssue =
      process.env.API_URL!.includes("://192.168") &&
      e.message === "Network request failed";

    if (isLocalhostIssue) {
      console.log(e);
      // fall through as though we succeeded
    } else {
      Sentry.Native.captureException(e);
      throw e;
    }
  } finally {
    setCodeVerifierHeader(null);
  }
  DeviceEventEmitter.emit("focus");
}

and added state: false to the google providers in [...nextauth].js.

  1. Yeah, useSession has been working for me exactly how the web one works.

  2. I don't anymore, I just have json APIs in Next.js, but originally I had both and this should work for it (due to the if/else in the providers block).

@Xodarap
Copy link
Contributor

Xodarap commented Jan 9, 2021

Thanks @mjewell ! It turns out that the issue I was having is that Expo doesn't like when you console.log the response from fetch, and I was confusing that for an actual error with the fetch.

Would be possible for you to share a snippet where you are using useSession? I'm still kind of confused about how this works – we do a valid request, and the Web server returns a cookie, but how do we get next auth to know that it's supposed to use that cookie going forward?

@mjewell
Copy link
Author

mjewell commented Jan 9, 2021

I'm actually not sure how it works, it seems like the cookie is correctly being sent by the app. I read that cookies are unreliable in apps but it seems to be working fine for me.

I'm not really sure what to even show a snippet of, I just render the Provider at the root of the app, and call useSession in any components I want the session in.

@Xodarap
Copy link
Contributor

Xodarap commented Jan 9, 2021

Okay, I have created a working standalone Expo app which should work as a starting point for anyone who wants to try this in the future

I found that my cookies were not being cleared between logins, causing authentication problems because it was sending stale session tokens. This app just clears all cookies before logging in each time which is probably a bad solution but it works as a starting point.

Thanks so much for your help @mjewell !

@bratsos
Copy link

bratsos commented Jan 11, 2021

@iaincollins Would you mind chiming in here and give your insights about the proposed solution, or what's the best way of achieving a unified authentication process in your opinion?

Ideally, I think everyone would love to be able to use the simplicity of a useSession hook, but I'm not sure I can imagine how this would work reliably with cookies/jwt on the expo/RN side without at least having the user to login repeatedly.

For everyone interested, I've opened a feature request on the expo's side to also get their opinion on that.

@mjewell
Copy link
Author

mjewell commented Jan 12, 2021

Glad I could help. @Xodarap Can you describe a bit more what the issue was? I have been using it this way for a while and haven't had issues.

@Xodarap
Copy link
Contributor

Xodarap commented Jan 14, 2021

I'm honestly not sure; I've experienced this with normal next auth once or twice as well. Basically, somehow the browser gets multiple copies of the session token cookie, and then next auth only looks at the first one (which I think is the oldest), so it doesn't work.

I assume it's supposed to be impossible to have multiple cookies at the same time; I'm not sure how it happens.

@niklasgrewe
Copy link

@Xodarap @mjewell Hi, I am also interested in using next-auth in react native. Is there any news here? Do your examples still work, even without expo?

@Xodarap
Copy link
Contributor

Xodarap commented Apr 18, 2021

I ended up putting this project on hold but it seemed like it was working fine. I didn't try it without Expo but I assume there shouldn't be too many changes needed.

@ahmedelq
Copy link

Any updates on this? I'd like to have Next.js's API routes as a backend for my React Native app.

@leerobert
Copy link

Any updates on this?

@balazsorban44
Copy link
Member

#2294

@jrmaktub
Copy link

jrmaktub commented Sep 6, 2022

@mjewell Could you provide a link to your project please? Thanks!

@EloB
Copy link

EloB commented Feb 1, 2023

Is this broken in version 4?

The provider functions no longer contain params.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Relates to documentation help needed The maintainer needs help due to time constraint/missing knowledge question Ask how to do something or how something works stale Did not receive any activity for 60 days
Projects
None yet
Development

No branches or pull requests