---
title: "Deep Links With Authentication in React Navigation"
description: "Learn various approaches to how to make deep links work when the screen is behind authentication in React Navigation."
url: https://www.callstack.com/blog/deep-links-with-authentication-in-react-navigation
updated: 2026-10-25T20:07:25.443Z
---

# Deep Links With Authentication in React Navigation

Learn various approaches to how to make deep links work when the screen is behind authentication in React Navigation.

_By Satyajit Sahoo, Software Engineer @ Callstack · Published 2025-12-02 · Category: React Native_

Deep links enable you to link directly to a specific screen in your app from external sources, such as social media, emails, and notifications. They are great for improving user engagement and providing a seamless user experience.

Things get a little tricky if your app requires users to log in, though. If the user is not already logged in when opening the deep link, they will be directed to the login screen instead. To ensure a smooth user experience, after they successfully log in, it would be ideal to redirect them to the appropriate screen they originally intended to visit.

However, if you have set up deep links with [React Navigation](https://reactnavigation.org/), you may have noticed that this is not handled out of the box. In this post, we'll go through a few approaches to implement this flow in older versions of React Navigation, and look at a new API that makes it much easier.

## What we'll cover

In this post, we'll use [React Navigation's static configuration API](https://reactnavigation.org/docs/getting-started#static-configuration) with [automatic deep links](https://reactnavigation.org/docs/configuring-links/#how-does-automatic-path-generation-work) for a simpler setup; however, the concepts can be applied to custom linking configurations as well.

For our example, let's say we have an app with the following screens:

- Accessible without login:
  - `SignIn`
- Accessible after login:
  - `Home`
  - `Profile`

What we'd like to achieve:

- If the user opens a deep link to `/profile` while not logged in, they should be taken to the `SignIn` screen.
- After successfully logging in, they should be redirected to the `Profile` screen.
- After logging out and logging back in, if there was no deep link, they should be taken to the `Home` screen.

This should also work regardless of whether the app was started from a cold state (not running in the background) or was already running in the background.

You can see our desired behavior in the demo video:

Your browser does not support the video tag.

In the demo, you can see that the deep link to the `profile` was not handled before login due to conditional screens, resulting in a warning. Still, it gets handled after login and shows the `Profile` screen.

Our navigator will be a stack navigator with two [groups](https://reactnavigation.org/docs/static-configuration/#groups): `authenticated` and `unauthenticated`, which will conditionally render screens based on the user's authentication status using the [`if`](https://reactnavigation.org/docs/static-configuration/#if) property.

```javascript
const RootStack = createNativeStackNavigator({
  groups: {
    authenticated: {
      if: useIsSignedIn,
      screens: {
        Home: {
          screen: Home,
        },
        Profile: {
          screen: Profile,
        },
      },
    },
    unauthenticated: {
      if: () => !useIsSignedIn(),
      screens: {
        SignIn: {
          screen: SignIn,
        },
      },
    },
  },
});
```

Then, we use the navigator with automatic deep links enabled:

```javascript
const Navigation = createStaticNavigation(RootStack);

export function App() {
  return (
    <Navigation
      linking={{
        enabled: "auto",
        prefixes: [createURL("/")],
      }}
    />
  );
}
```

If you are not familiar with automatic deep links, they automatically generate deep link paths based on screen names, e.g. ,`profile` to go to the `Profile` screen.  You can read more about it in [React Navigation docs](https://reactnavigation.org/docs/configuring-links/#how-does-automatic-path-generation-work).

## Manual approaches

In older versions of React Navigation, there is no built-in way to handle this flow, so we need to implement it manually. Here are two main approaches:

### Approach 1: Remounting the navigation container

React Navigation always handles deep links on mount. So we can take advantage of this behavior to handle deep links after login by remounting the navigation container. This can be done by using a key prop on the `Navigation` component (or `NavigationContainer` in the dynamic API):

```javascript
export function App() {
  const isSignedIn = useIsSignedIn();
  const lastDeepLink = useLastDeepLink();

  return (
    <Navigation
      key={isSignedIn ? "signed-in" : "signed-out"}
      linking={{
        enabled: "auto",
        prefixes: [createURL("/")],
        getInitialURL: () => lastDeepLink,
      }}
    />
  );
}
```

Here, we change the [`key`](https://react.dev/reference/react/useState#resetting-state-with-a-key) prop based on the authentication status. This forces React to unmount and remount the navigation container when the user logs in or out.

The `useLastDeepLink` hook is a custom hook that keeps track of the last deep link URL opened, and clears it after the user logs in:

```javascript
function useLastDeepLink() {
  const isSignedIn = useIsSignedIn();

  // Keep track of the incoming deep links to handle after logging in
  const [deepLink, setDeepLink] = useState(() =>
    isSignedIn ? null : Linking.getInitialURL()
  );

  useEffect(() => {
    // Clear initial URL after logging in
    // This prevents handling the same deep link again after logging out and logging back in
    if (isSignedIn) {
      setDeepLink(null);
    }

    // Store incoming deep links if not authenticated
    // They will be handled after logging in
    const subscription = Linking.addEventListener("url", ({ url }) => {
      if (!isSignedIn) {
        setDeepLink(url);
      }
    });

    return () => {
      subscription.remove();
    };
  }, [isSignedIn]);

  return deepLink;
}
```

We need to manually track the last deep link and clear it after login, otherwise, it will lead to some issues:

- Logging out and logging back would reuse initial deep link, so we need to clear it after first login.
- If a deep link is opened while the user is already on login screen, it won't be handled after login unless we keep track of it and pass it to the navigation container, as React Navigation only handles the initial URL on mount.

Disadvantages:

- There is no animation between screen transitions, and depending on the navigator, it may flicker.
- The entire navigation state is reset, it's not possible to do it only in a nested navigator.
- It needs manual tracking of deep links to avoid issues.

Another variant of this approach is to show a login screen first without rendering the navigation container at all. However, it still has the same disadvantages as above and doesn't work for more complex flows where you may want to navigate to other screens from the login screen (e.g., sign up, forgot password, etc.).

### Approach 2: Manually navigating to the deep link after login

As an alternative, we can track incoming deep links and, when the user logs in, handle the deep link ourselves and navigate to the corresponding screen.

To keep track of incoming deep links, we can use the same `useLastDeepLink` hook used in the previous section. Then, we'd parse the deep link URL and navigate to the appropriate screen after login:

```javascript
const linking = {
  enabled: 'auto',
  prefixes: [createURL('/')],
};

// Get the deep link configuration for our screens
// This is used by React Navigation to parse the incoming URLs internally
// But since we are handling deep links manually, we need this for parsing
const linkingPathConfig = createPathConfigForStaticNavigation(
  RootStack,
  // Pass `linking.options` if present
  undefined,
  true,
);

export function App() {
  const ref = useNavigationContainerRef();
  const authenticated = useAuth((state) => state.authenticated);

  const lastDeepLink = useLastDeepLink();

  // Handle the stored deep link after logging in
  useEffect(() => {
    const handleDeepLink = async () => {
      const url =
        typeof lastDeepLink === 'string' ? lastDeepLink : await lastDeepLink;

      if (ref.isReady() && url && linkingPathConfig) {
        // Strip the prefix from the URL
        const path = linking.prefixes.reduce((acc, prefix) => {
          if (acc.startsWith(prefix)) {
            return acc.slice(prefix.length);
          }

          return acc;
        }, url);

        // Get the navigation state from the path
        const state = getStateFromPath(path, { screens: linkingPathConfig });

        // If we have a valid state, get the action and dispatch it
        // This will navigate to the intended screen
        if (state) {
          const action = getActionFromState(state);

          ref.dispatch(action!);
        }
      }
    };

    if (authenticated && lastDeepLink) {
      handleDeepLink();
    }
  }, [authenticated, ref]);

  return <Navigation ref={ref} linking={linking} />;
}
```

Here, we check if there is a stored deep link after the user logs in. If there is, we parse the URL to get the navigation state, then derive the navigation action from that state and dispatch it to navigate to the intended screen.

After stripping the prefix from the deep link URL, we parse it with [`getStateFromPath`](https://reactnavigation.org/docs/navigation-container/#linkinggetstatefrompath) from `@react-navigation/native` to match how React Navigation parses it. It takes our linking options as an argument, which we can also generate with [`createPathConfigForStaticNavigation`](https://reactnavigation.org/docs/static-configuration/#createpathconfigforstaticnavigation) when using the static API.

This is similar to how React Navigation handles deep links internally, but we are doing it manually after login.

Disadvantages:

- Requires more manual work and things to keep track of.
- There will be a separate navigation after login, which may not feel as smooth.
- The initial route will always be in the stack, which may not be desirable in some cases.

## Built-in API in React Navigation 7

React Navigation 7 adds a new API to handle this use case more seamlessly. If you are on the latest version of all `@react-navigation` packages, you can enable it by setting [`UNSTABLE_routeNamesChangeBehavior`](https://reactnavigation.org/docs/navigator/#route-names-change-behavior) to `'lastUnhandled'` in the navigator that handles authentication state changes:

```javascript
const RootStack = createNativeStackNavigator({
  UNSTABLE_routeNamesChangeBehavior: 'lastUnhandled',

  // ... rest of the navigator config
});
```

To understand what this does, let's review what happens when the authentication state changes:

- When the user logs in, the list of available screens (names of routes) changes based on the new authentication state.
- If none of the previously rendered screens are available anymore, React Navigation shows the first screen in the new list of available screens.

The `UNSTABLE_routeNamesChangeBehavior` option essentially lets us customize this behavior. By default, it's set to `firstMatch`, which means it will show the first screen in the new list of available screens.

By setting it to `lastUnhandled`, we tell React Navigation to remember any unhandled actions (like deep links) that were attempted before the authentication state changed. So when the user logs in, if there was a deep link that couldn't be handled before, React Navigation will retry handling that deep link after the authentication state changes, navigating the user to the intended screen.

This approach has several advantages:

- It's built-in and requires minimal code, we only need to set a single option in the navigator.
- The navigation is seamless, with proper animations between screens.
- It works well with nested navigators, only the navigator where the authentication state changes needs to have this option set.

However, as the name suggests, this API is marked as unstable, which means it may change in future releases depending on user feedback and further testing. So it is the perfect time to try it out and provide feedback. You can post your thoughts and feedback in the [React Navigation GitHub discussions](https://github.com/react-navigation/react-navigation/discussions).

## Demo app

You can find a demo app implementing the approaches discussed in this post in this [GitHub repository](https://github.com/callstackincubator/react-navigation-deep-link-auth-demo).

The repository has three branches, each implementing a different approach:

- [`remount-container`](https://github.com/callstackincubator/react-navigation-deep-link-auth-demo/tree/remount-container): Remounting the navigation container after login.
- [`manual-navigate`](https://github.com/callstackincubator/react-navigation-deep-link-auth-demo/tree/manual-navigate): Manually navigating to the deep link after login.
- [`main`](https://github.com/callstackincubator/react-navigation-deep-link-auth-demo/tree/main): Built-in unstable API.

The app can be run with [Expo Go](https://expo.dev/go).

To test the deep links, you can use the [`uri-scheme`](https://www.npmjs.com/package/uri-scheme/) utility by [Expo](https://expo.dev/), for example:

```bash
npx uri-scheme open 'exp://192.168.0.31:8081/--/profile' --ios
```

In this case, the `exp://192.168.0.31:8081` prefix refers to the server URL of the Expo Go app running on your machine, and the deep link path is `/profile`.

## Conclusion

These approaches show how handling deep links is challenging when authentication is involved, with many gotchas and complexity when trying to handle them manually. I tried to solve these problems with the new [`UNSTABLE_routeNamesChangeBehavior`](https://reactnavigation.org/docs/navigator/#route-names-change-behavior) option in React Navigation 7, and tried to keep it simple to use without the need for complex code and workarounds.

If your app relies on deep links, be sure to give this a try, and don’t forget to [provide feedback](https://github.com/react-navigation/react-navigation/discussions).
