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

How to handle auth #14

Closed
bndkt opened this issue Sep 28, 2022 · 12 comments · Fixed by #34
Closed

How to handle auth #14

bndkt opened this issue Sep 28, 2022 · 12 comments · Fixed by #34
Labels
help wanted Extra attention is needed question Further information is requested

Comments

@bndkt
Copy link

bndkt commented Sep 28, 2022

Hi Evan,

as I said on Twitter already, awesome work on the router! I've started implementing it today and have removed sooo much code. My app has gotten a 100% more declarative. One thing I struggle wrapping my head around is auth. And I think it's such an important part of almost every app that it's worth having a best practice in the docs. I'm happy to contribute a PR for that once I've figured it out for myself. Before Expo Router, I was basically doing the following:

<Stack.Navigator>
      {isLoggedIn ? (
        <Stack.Screen name="Authenticated" component={Authenticated} />
      ) : isLoggedOut ? (
        <Stack.Screen name="SignIn" component={SignIn} />
      ) : (
        <Stack.Screen name="Loading" component={Loading} />
      )}
</Stack.Navigator>

This way, only the relevant screens (and dependents) were mounted based on the auth state. Now with file-based routing, the files will always be there. I'm sure you have thought about auth already, any best practice you have in mind?

@fristyr
Copy link

fristyr commented Sep 28, 2022

Same question!

@iam-rohid
Copy link

I think you have to handle it how you handle routes in nextjs or svelteKit project on the web. Just navigate to another page based on the state

@fristyr
Copy link

fristyr commented Sep 28, 2022

Would be nice to have something like middle wares in next js. So with one file we could check if user is authenticated or not and based on that filter routes

@c0nsoleg
Copy link

c0nsoleg commented Sep 28, 2022

Can't we just check auth in layouts and navigate somwhere else if not authorized?

@EvanBacon
Copy link
Contributor

No docs or built-in solution for this yet. If anyone wants to propose a solution for protected routes I can start building off it. There probably won't be anything like middleware in the short term so redirecting is probably the way to go.

@EvanBacon EvanBacon added help wanted Extra attention is needed question Further information is requested labels Sep 28, 2022
@fristyr
Copy link

fristyr commented Sep 28, 2022

We probably can still use something like this. Or will other routes still be accessible?
And inside of this root, place some conditionals/hooks to just navigate back to the login screen if !isLoggedIn .

app\(root).tsx
import { NativeStack } from "expo-router";

export default function Layout() {
  return (
    <NativeStack>
      {isLoggedIn ? (
        <NativeStack.Screen
          name="(tab)"
          options={{
            headerShown: false,
          }}
        />
      ) : isLoggedOut ? (
        <NativeStack.Screen
          name="modal"
          options={{
            presentation: "modal",
          }}
        />
      ) : (
        <NativeStack.Screen
          name="modal"
          options={{
            presentation: "modal",
          }}
        />
      )}
    </NativeStack>
  );
}

@Albert-Gao
Copy link

i have a web version of this component here : #20

almost working, just not working for accessing directly from browser URL

@bndkt
Copy link
Author

bndkt commented Sep 29, 2022

@fristyr this doesn't work because all the routes are accessible by virtue of the files existing, so it doesn't matter if we only render them declaratively depending on some variable/state. What I've done now (as suggested by @rohid2d and @EvanBacon) is implement navigation depending on auth state:

export default function Layout() {
  const { isSignedIn, isSignedOut } = useAuth();
  const link = useLink();

  useEffect(() => {
    const targetRoute = isSignedIn ? "/" : isSignedOut ? "/signIn" : "/loading";
    link.push(targetRoute);
  }, [isSignedIn, isSignedOut, link]);

  return (
    <NativeStack
      screenOptions={{ headerShown: false }}
      initialRouteName="loading"
    />
  );
}

But I still think it would be better to somehow only render routes that are valid depending on nav state instead of just navigating away. This is also what's recommended by React Navigation (https://reactnavigation.org/docs/auth-flow/), but so far, I don't have any idea how to achieve this with file system based routing. One option would be to explicitly disable routes, but React Navigation doesn't seem to have a provision for this.

@fristyr
Copy link

fristyr commented Sep 29, 2022

@bndkt agree with you here

@Albert-Gao
Copy link

Albert-Gao commented Sep 29, 2022

Ideally, when you use file-based navigation, the folder structure could match your application state for a visually reasoning

image

you can have this (auth) and (non-auth) fragment for wrapping the logic for auth and non-auth state, this is the layer where you put the < Providers> and < Protected>

image

as shown in the image, the only additional wrapper in the (auth) is the < Protected> component, and the < AfterAuthLayout>

so instead of doing the usual react-navigation way, to swap out the whole navigation stack, you simply just list all routes in the app folder, and protect the part that needs to be protected.

have a quick example here:
https://github.com/Albert-Gao/expo-router-web-try-out/tree/web-auth-flow

currently built for web, but i believe it works on mobile as well (you just have to add the < Stack> wrapper to the layout component). (https://github.com/Albert-Gao/expo-router-web-try-out/tree/web-auth-flow)

I found this is easy to reason about in 1sec about which route is protected in scale

image

Evan just added the link.replace() function to the 0.0.20 version, which will make this pattern work

image

which through my testing, is not working, or maybe i am missing something here,

but for the mobile app,

it should be already WORKING since you do not have that user-can-access-any-page-by-url problem.

@bndkt
Copy link
Author

bndkt commented Sep 29, 2022

@EvanBacon I thought about this some more. The solution I described above does work, but it still throws an error because I try to route before the nav is ready ("The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting."). I could use navigationRef.isReady(), but I can't access the navigationRef (https://reactnavigation.org/docs/navigating-without-navigation-prop/).

I supposed this could be solved somehow (e.g. by Expo Router creating a navigationRef by default and exposing it), but the more I think about it, the more I'm convinced that there should be a way to opt out of creating routes automatically for use cases such as auth where I want to determine which routes are available based on state.

What do you think about a convention like prefixing file names with "." or "_" so that for those files, routes are not created automatically and have to be handled by the developer. Then I could have a layout route (auth) that renders a stack, and within that I'd have ".loading.js", ".signIn.js" and ".(tab).js" but in this case, I'm handling the route creation myself based on auth state:

<NativeStack>
  {isLoggedIn ? (
    <NativeStack.Screen name=".(tab)" />
  ) : isLoggedOut ? (
    <NativeStack.Screen name=".signIn" />
  ) : (
    <NativeStack.Screen name=".loading" />
  )}
</NativeStack>

@chrisdrackett
Copy link

chrisdrackett commented Sep 30, 2022

@bndkt I like this idea of a file decorator, but in my mind it could be used as a way to not have to add extra logic for a route within code. For example a route like _myProfile.ts would only work if the user is authenticated. Obviously there needs to be a way somewhere to let the router know if the user is authenticated and how to authenticate the user if they are not. But I really like that you create a screen that requires auth without having to touch anything other than the file name.

I suppose one downside here is that if you are working on an app where most views require auth you are having to decorate basically every route name, but it still seems more dev friendly than having to add screens to a stack and then adding options to each of the screens. You could swap that paradigm, but then those creating open webapps without much or any auth are having to decorate a lot of views…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants