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

Support react-router v6 #178

Closed
2 of 9 tasks
baumandm opened this issue Jul 30, 2020 · 84 comments · Fixed by #275
Closed
2 of 9 tasks

Support react-router v6 #178

baumandm opened this issue Jul 30, 2020 · 84 comments · Fixed by #275

Comments

@baumandm
Copy link

I'm submitting this issue for the package(s):

  • jwt-verifier
  • okta-angular
  • oidc-middleware
  • okta-react
  • okta-react-native
  • okta-vue

I'm submitting a:

  • Bug report
  • Feature request
  • Other (Describe below)

Current behavior

The upcoming version of React-router, v6, makes a number of breaking changes, specifically breaking <SecureRoute /> due to the deprecation of useHistory() and render props. That is the only breaking feature I've encountered, but there may be others.

Expected behavior

This library should support react-router v6.

Minimal reproduction of the problem with instructions

Use the following react-router-dom dependency:

  "react-router-dom": "^6.0.0-beta.0",

Extra information about the use case/user story you are trying to implement

I've implemented a custom <SecureRoute /> component that is sufficient for my use cases. It's slightly simplified so it might not be a drop-in-replacement for all cases, but I wanted to share in case anyone else needs an interim solution.

import { useOktaAuth } from '@okta/okta-react';
import React from 'react';
import { Route } from 'react-router-dom';

const RequireAuth = ({ children }) => {
  const { authService, authState } = useOktaAuth();

  if (!authState.isAuthenticated) {
    if (!authState.isPending) {
      const fromUri = window.location.href;
      authService.login(fromUri);
    }
    return null;
  }

  return <React.Fragment>{children}</React.Fragment>;
};

const SecureRoute = ({ element, ...props }) => {
  const WrappedComponent = (wrappedProps) => <RequireAuth>{element}</RequireAuth>;
  return <Route {...props} element={<WrappedComponent />} />;
};

export default SecureRoute;

Environment

  • Package Version: 3.0.2
  • Browser: Firefox
  • OS: MacOS
  • Node version (node -v): v14.3.0
  • Other:
@swiftone
Copy link
Contributor

@baumandm - Thanks for the ticket and the interim implementation. We'll put this on the backlog and work to make sure we have compatibility with react-router 6 ASAP following the official release. Given the scope of some of the changes, this will likely require a major version change on our part.

@swiftone
Copy link
Contributor

Internal ref: OKTA-318565

@bartimaeus
Copy link

@swiftone I have been experiencing an issue with too. I'm using "react-router-dom": "^5.2.0".

Thanks @baumandm! I spent half a day debugging my app trying to figure out why after upgrading @okta/okta-react and @okta/okta-signin-widget my nested routes weren't working correctly. Finding your comment saved me!

I thought the issue was only a problem with nested routes, but then discovered that my component referenced in <SecureRoute path="/" component={<AuthenticatedLayout/>} /> would re-render each time I navigated or changed routes.

The useEffect added here was the issue for me.

I used the latest version of <SecureRoute/>, and reverted the useEffect.

- useEffect(() => {
-   // Make sure login process is not triggered when the app just start
-   if(!authState.isAuthenticated && !authState.isPending) { 
-     const fromUri = history.createHref(history.location); 
-     authService.login(fromUri);
-   }  
- }, [authState, authService]);
- 
- if (!authState.isAuthenticated) {
-   return null;
- }
+ if(!authState.isAuthenticated) { 
+   if(!authState.isPending) {
+     // Using @baumandm's suggestion here for react-router-dom v6
+     const fromUri = window.location.href;
+     authService.login(fromUri);
+   }
+   return null;
+ }

@shuowu
Copy link
Contributor

shuowu commented Aug 14, 2020

@bartimaeus Thanks for reporting the issue!

Internal Ref: OKTA-322622 (Duplicate)

@swiftone
Copy link
Contributor

@bartimaeus - Your issue sounds separate from react-router v6 (beta), and sounds more like okta/okta-oidc-js#777 (which is on my current sprint)

@bartimaeus
Copy link

@swiftone you're right. okta/okta-oidc-js#777 is my exact issue.

@denysoblohin-okta denysoblohin-okta transferred this issue from okta/okta-oidc-js Nov 3, 2021
@relm923
Copy link

relm923 commented Nov 8, 2021

@swiftone any update on this now that v6 has officially been released?

@Steel9561
Copy link

Can someone create a TypeScript version of this code? 👍

import { useOktaAuth } from '@okta/okta-react';
import React from 'react';
import { Route } from 'react-router-dom';

const RequireAuth = ({ children }) => {
const { authService, authState } = useOktaAuth();

if (!authState.isAuthenticated) {
if (!authState.isPending) {
const fromUri = window.location.href;
authService.login(fromUri);
}
return null;
}

return <React.Fragment>{children}</React.Fragment>;
};

const SecureRoute = ({ element, ...props }) => {
const WrappedComponent = (wrappedProps) => {element};
return <Route {...props} element={} />;
};

export default SecureRoute;

@Steel9561
Copy link

I'm submitting this issue for the package(s):

  • jwt-verifier
  • okta-angular
  • oidc-middleware
  • okta-react
  • okta-react-native
  • okta-vue

I'm submitting a:

  • Bug report
  • Feature request
  • Other (Describe below)

Current behavior

The upcoming version of React-router, v6, makes a number of breaking changes, specifically breaking <SecureRoute /> due to the deprecation of useHistory() and render props. That is the only breaking feature I've encountered, but there may be others.

Expected behavior

This library should support react-router v6.

Minimal reproduction of the problem with instructions

Use the following react-router-dom dependency:

  "react-router-dom": "^6.0.0-beta.0",

Extra information about the use case/user story you are trying to implement

I've implemented a custom <SecureRoute /> component that is sufficient for my use cases. It's slightly simplified so it might not be a drop-in-replacement for all cases, but I wanted to share in case anyone else needs an interim solution.

import { useOktaAuth } from '@okta/okta-react';
import React from 'react';
import { Route } from 'react-router-dom';

const RequireAuth = ({ children }) => {
  const { authService, authState } = useOktaAuth();

  if (!authState.isAuthenticated) {
    if (!authState.isPending) {
      const fromUri = window.location.href;
      authService.login(fromUri);
    }
    return null;
  }

  return <React.Fragment>{children}</React.Fragment>;
};

const SecureRoute = ({ element, ...props }) => {
  const WrappedComponent = (wrappedProps) => <RequireAuth>{element}</RequireAuth>;
  return <Route {...props} element={<WrappedComponent />} />;
};

export default SecureRoute;

Environment

  • Package Version: 3.0.2
  • Browser: Firefox
  • OS: MacOS
  • Node version (node -v): v14.3.0
  • Other:

I tried creating a similar but different implementation, but it would seem that for some reason it's still looking for the SecureRoute that comes with OktaReact and not the custom one I created.

I did verify that the SecureRoute links are using the custom SecureRoute (not the okta one that is picked from the bundle file). Here is error in chrome browser:

"Uncaught Error: [SecureRoute] is not a component. All component children of must be a or <React.Fragment>"

Any ideas please to help with this?

@baumandm
Copy link
Author

The latest version of React Router v6 breaks SecureRoute because all the children of <Routes /> must be <Route />. This means creating a custom Route wrapper is off the table.

The solution I've found was to rewrite SecureRoute to do an auth check and conditionally render its children. Then it's can be used inside a standard <Route />, which does the route matching:

<Routes>
  <Route path="/admin" element={<SecureRoute><Private /></SecureRoute>} />
</Routes>
``

Here's a blog post with more details: https://dev.to/iamandrewluca/private-route-in-react-router-v6-lg5

@Steel9561
Copy link

<Route path="/admin" element={} />

Thanks so much! will give this a try. Do you happen to have a typescript version of your custom secureroute component by any chance?

@Steel9561
Copy link

The latest version of React Router v6 breaks SecureRoute because all the children of <Routes /> must be <Route />. This means creating a custom Route wrapper is off the table.

The solution I've found was to rewrite SecureRoute to do an auth check and conditionally render its children. Then it's can be used inside a standard <Route />, which does the route matching:

<Routes>
  <Route path="/admin" element={<SecureRoute><Private /></SecureRoute>} />
</Routes>
``

Here's a blog post with more details: https://dev.to/iamandrewluca/private-route-in-react-router-v6-lg5

This example helped, but now I am getting this error:

Cannot destructure property 'oktaAuth' of '(0 , okta_context__WEBPACK_IMPORTED_MODULE_1_.useOktaAuth)(...)' as it is null.

@Steel9561
Copy link

Now I am getting a new error saying that all Route needs to be wrapped up around a Routes container. Its weird because I have all my Route components inside the Routes component. Any ideas?

@swiftone
Copy link
Contributor

swiftone commented Nov 16, 2021

@relm923, @andreawyss, @dsabanin - I'm no longer with Okta myself, so I'm afraid I can't help on this issue, but I'm sure the Okta crew will be responding soon.

If you have an immediate need, my unofficial recommendation is to implement your own SecureRoute implementation as @baumandm suggested. If I recall correctly, it's a fairly thin layer that uses useOktaAuth to decide if the route should render or not.

Edit: https://github.com/okta/okta-react/blob/master/src/SecureRoute.tsx

@kellengreen
Copy link

What's the status of this? v6 is now live.

@jaredperreault-okta
Copy link
Contributor

We are aware of this issue and are working towards a solution. Stay tuned for updates.

In the meantime, if upgrading to the v6 router is required, I suggest writing your own version of SecureRoute as suggested in this thread

Internal Ref: OKTA-447325

@Suresh-Lakum
Copy link

Suresh-Lakum commented Nov 30, 2021

Any update on this? I am facing similar issue and using latest react-router-dom v6. I can't include SecureRoute directly as part of Routes and

<Route path="/" element={ <SecureRoute> <DashboardDefault /> </SecureRoute> } />
isn't working either. Getting below error :
image

@jonrimmer
Copy link

@jaredperreault-okta I have tried writing my own version of SecureRoute as you suggest, but I get the following error when trying to build my app:

node_modules/@okta/okta-react/bundles/okta-react.esm.js:21:9: error: No matching export in "node_modules/react-router-dom/index.js" for import "useRouteMatch"

What is your recommended workaround for okta-react importing useRouteMatch when it no longer exists in react-router-dom?

@jaredperreault-okta
Copy link
Contributor

@jonrimmer
Copy link

jonrimmer commented Jan 27, 2022

@jaredperreault-okta This usage in your codebase, not mine. ESBuild is tracing the imports in your esm bundle and finding invalid imports.

@dubzzz
Copy link

dubzzz commented Jan 28, 2022

Why isn't react-router-dom a dependency of the package (with ^ and not >=)? Having it into peerDependencies makes bundlers fail whenever you have to pull react-dom@6.x.y in a project having okta-react.

I know that the use of react-router-dom is limited to SecureRoute (that I do not use on my project) but the fact to have a peer instead of a clear dep makes bundlers totally fail even if you don't use anything linked to it.

@jaredperreault-okta
Copy link
Contributor

@dubzzz react-router-dom is a peer dependency because the version used inside our sdk and the application need to be the same. However, this is something we are currently re-evaluating

@jonrimmer a release is going out soon that should address the useRouteMatch issue

@jaredperreault-okta
Copy link
Contributor

To provide an update here

We are still discussing React routing holistically, including react-router-dom v6. In the meantime we have put together a POC test app to illustrate the usage of various React routing libraries

https://github.com/okta/okta-react/tree/routing-poc/test/apps/react-routing

Would love some community feedback

@baumandm
Copy link
Author

baumandm commented Feb 3, 2022

I tested out v6.4.2 but I'm still getting Typescript errors despite the change in import:

.../node_modules/@okta/okta-react/bundles/okta-react.esm.js
Attempted import error: 'useRouteMatch' is not exported from 'react-router-dom' (imported as 'ReactRouterDom').

Any ideas?

@jaredperreault-okta
Copy link
Contributor

@baumandm Are you using <SecureRoute /> in your application?

@baumandm
Copy link
Author

baumandm commented Feb 3, 2022

I am still using a custom implementation from the beginning of the thread, not importing the built-in one.

It's very possible the error is due to my tsconfig or project setup specifically, but I looked through your sample project to compare and can't see the issue.

Happy to wait for the long-term fix before upgrading.

@jaredperreault-okta
Copy link
Contributor

jaredperreault-okta commented Jun 8, 2022

To provide an update on this:

Currently we do not plan on adding a SecureRoute component for react-router v6 to this sdk. We have created a series of samples (found here) illustrating how to implement Okta with a few popular react routing libraries (react-router@6.x being one of them)

Main reason for this decision: SecureRoute forces react-router to be in our dependency tree

First, we do not want to position this sdk so it forces the use of other dependencies (like react-router) by downstream applications. We are currently in violation of this tenet.

Second, react-router is listed as a peer dependency of okta-react. This means if we release a new okta-react version that bumps react-router to v6, ALL downstream applications would need to migrate to v6 in order to upgrade okta-react. We place a high priority on making migrations to the latest versions of sdks to be as seamless as possible because our sdk updates sometimes contain vulnerability fixes and security updates

@kenyee
Copy link

kenyee commented Jun 12, 2022

First...thanks for the examples...that was enough to get things working w/ React Router V6.

Is there any current guidance/example on using the JWT access token for Redux Toolkit's RTK Query?
There's a much older sample (not using the useAuthState or the current okta-react SDK) that seems to handle this sample by having the AuthHandler call a slice to set the auth info into the store: https://developer.okta.com/blog/2019/08/12/build-secure-react-application-redux-jwt.

e.g. something like this I'd expect to work but doesn't (authState is always undefined):

const oktaAuth = new OktaAuth(oktaAuthConfig);
const AppRoutes = () => {
    const history = useHistory();

    const userInfo = useAuthUser();
    const {authState} = useOktaAuth() || {};
    useEffect(() => {
        console.log("Authstate is " + JSON.stringify(authState))
        if (authState) {
            if (authState.isAuthenticated) {
                const user: CurrentUser = {
                    id: String(userInfo?.ldap),
                    displayName: String(userInfo?.name),
                    accessToken: String(authState.accessToken)
                }
                setAuthSuccess(user)
            } else {
                setLogOut()
            }
        }
    }, [authState])

@jaredperreault-okta
Copy link
Contributor

@kenyee To my knowledge we do not have a sample specifically for Redux Toolkit's RTX Query api (other than the blog post you mentioned). If you need assistance with this integration, I suggest opening a new issue with more details (and code snippets).

At a quick glance however, I would ensure okta-react's <Security> component is mounted above <AppRoutes>. useOktaAuth returns a React Context, where authState initializes to undefined, but is updated via <Security>. authState should only be undefined for a brief moment during bootstrapping/initial load

@alexspence
Copy link

If we aren't going to get a secure routes component for react router v6 can we get the example updated to something that works? The current implementation is a bit half baked and has some really strange behavior like replaying the implicit authentication flow on refresh.

@rapaccinim's solution above does resolve some of these issues but when the token expires things stop working until you refresh. It seems like there is some functionality in the original SecureRoute component that is not implemented in the example for react-router-v6.

@kenyee
Copy link

kenyee commented Jun 13, 2022

we do not have a sample specifically for Redux Toolkit's RTX Query api (other than the blog post you mentioned). If you need assistance with this integration, I suggest opening a new issue with more details (and code snippets).

Wrote this up since I'm sure you're going to get more questions on RTK Query since they're pushing folks to use it for network calls in the official React docs..
#236

@kellengreen
Copy link

Without SecureRoute the usefulness of this library seems greatly diminished. I understand not wanting to force users into a particular routing library, so perhaps this logic can be spun off into @okta/react-router-dom?

@rlazimi-dev
Copy link

rlazimi-dev commented Jun 30, 2022

I'd say a decent workaround for now is the following pattern: redirect to login if not authenticated while in the function components you'd like to secure.

The logic goes as follows:

  1. check if authState ready and return arbitrary component if not
  2. otherwise if it is ready and you're not authenticated, then you get redirected to login
  3. otherwise if it is ready and you're authenticated, you run the code you want to run

For example if you have a HomePage function component:

function HomePage(props) {
  yourCode()
  return yourComponent()
}

Assuming your login page is '/login', add the following authentication code:

import { useNavigate } from 'react-router-dom'
import { useOktaAuth } from '@okta/okta-react'
import { useEffect, useState } from 'react'

function HomePage(props) {

  //<-----------------------------------------BEGIN HERE 
  const navigate = useNavigate();
  const { authState, oktaAuth } = useOktaAuth();
  const [isAuthed, setAuthed] = useState(false)

  useEffect(() => {
    if (!authState) {
      setAuthed(false)
    }
    else {
      if (!authState.isAuthenticated) {
        navigate('/login')
      }
      setAuthed(true)
    }
  }, [oktaAuth, authState])

  if (!isAuthed) {
    return <p>checking for authentication...</p>
  }
  //<-----------------------------------------END HERE 

  yourCode()
  return yourComponent()
}

Pros: copying and pasting the modification in each function component you want to be protected
Cons: copying and pasting the modification in each function component you want to be protected

EDIT: don't copy and paste. gonna leave the boilerplate code here since it provides a good intuition for how the redirect works, but see @jaredperreault-okta's below for better code re-use.

@jaredperreault-okta
Copy link
Contributor

jaredperreault-okta commented Jun 30, 2022

I don't recommend copying and pasting this block into all components you want protected. Instead define a component that contains this block and mount all components you want protected as subroutes (utilizing react-router-dom's Outlet)

  return (
    <Routes>
      <Route path='/' element={<Home />} />
      <Route path='login/callback' element={<LoginCallback loadingElement={<Loading />} />} />
      <Route path='/protected' element={<RequiredAuth />}>
        <Route path='' element={<Protected />} />
        {/* place components requiring protection here */}
      </Route>
    </Routes>
  );

In this example, RequiredAuth will handle checking the authentication state and will render the subroute if the user is authenticated via <Outlet />

Full sample is here

@jaredperreault-okta
Copy link
Contributor

@alexspence The samples have been updated and should address the browser refresh issue you mentioned

@rlazimi-dev
Copy link

Ah sounds good @jaredperreault-okta – I was hoping someone would generalize that! (i tried but failed)

@taher267
Copy link

taher267 commented Aug 2, 2022

is it possible to use in react router v6?

@kellengreen
Copy link

kellengreen commented Aug 2, 2022

Yes see examples here.

TL;DR - You need to roll your own SecureRoute logic, but it works.

@henricook
Copy link

henricook commented Sep 11, 2022

As someone who pays tens of thousands a year to Okta this is disappointing. These examples might be helpful (thanks for those kellengreen), I'm going to have to sit and trawl through them and try to apply it to my implementation using <SecureRoute>. The Okta team should really post a clear walkthrough on how to use with/migrate to react router v6

@mraible
Copy link

mraible commented Sep 11, 2022

This example might be helpful:

https://github.com/okta-samples/okta-react-sample

It uses React Router v6 and you can set it up quickly if you install the Okta CLI:

okta start react

@mraymond77
Copy link

Since moving to react-router 6 with the custom SecureRoute style provided by @mraible and @kellengreen it is working and I'm happy enough with that. But seems like I get completely re-authed on every refresh or when typing in a URL and hitting enter. It jumps to my callback and comes back to the page. Wondering if there is a way around that behavior? I thought useOktaAuth() would store and retrieve from local storage and not wipe out a token stored there, and thus authState.isAuthenticated wouldn't reset on refresh.

@jaredperreault-okta
Copy link
Contributor

@mraymond77 this sample should fix your issue, it's iteration on the one @mraible provided

@mraymond77
Copy link

@jaredperreault-okta that did the trick, thanks!

@kellengreen
Copy link

kellengreen commented Oct 31, 2022

This is what I'm using at the moment. Cleaned up some bits from the sample for better clarity and reusability.

export default function Authenticated({ success, loading }) {
  const { oktaAuth, authState } = useOktaAuth()

  useEffect(() => {
    if (authState?.isAuthenticated === false) {
      const originalUri = toRelativeUrl(
        globalThis.location.href,
        globalThis.location.origin,
      )
      oktaAuth.setOriginalUri(originalUri)
      oktaAuth.signInWithRedirect()
    }
  }, [oktaAuth, authState?.isAuthenticated])

  return authState?.isAuthenticated === true ? success : loading
}

Something along these lines should really be included in the package. This way OKTA can remove the react-router-dom dependency, while keeping developer experience high.

<Authenticated
  success={<Outlet />}
  loading={<FullPageLoading />}
/>

@jchabotamica
Copy link

jchabotamica commented Jul 12, 2023

Any update here? Are we still not able to use react-router 6 without creating our own SecureRoute?

@kellengreen
Copy link

okta-react appears to be decoupling the react-router-dom dependency from future releases, so I'd assume support for this is not coming. Personally I actually agree that removing SecureRoute from this lib makes sense, and router specific components should reside elsewhere.

In the meantime the Authenticated example should help fill in the gaps:

<Route
  element={
    <Authenticated success={<Outlet />} />
  }
>
  <Route
    path="/secret"
    element={<Secret />}
  />
</Route>

@jchabotamica
Copy link

@kellengreen that does make sense in regards to decoupling

@jaredperreault-okta
Copy link
Contributor

We currently do not have an official SecureRoute component for react-router@6 on our roadmap. We have created this samples directory, illustrating how to integrate okta-react with popular routing libraries.

@igloo12
Copy link

igloo12 commented Aug 28, 2023

It would be awesome if the README was more explicit about what version of the react-router is supported. It gives the install command below which installs react-router 6. But the example code provided in the readme doesn't work with react-router 6. I only found out about the react-router 6 examples by reading this thread.

npm install --save react
npm install --save react-dom
npm install --save react-router-dom
npm install --save @okta/okta-auth-js   # requires at least version 5.3.1

@arunmp25
Copy link

arunmp25 commented Sep 29, 2023

Hi Is there any sample on how to implement the same with react RouterProvider() especially regarding where to place the <SecureRoute /> as all the routes are directly passed to the route provider

export default function App() {
  return <RouterProvider router={router} />;
}

This was referenced Feb 21, 2024
@Spartan-Hex-Shadow
Copy link

Hi everyone

We are upgrading an old application to the latest version okta-react and are encountering the same issue of SecureRoute no longer working after going to version Version 6. Will this update be released soon?

@kellengreen
Copy link

kellengreen commented Mar 18, 2024

This won't be fixed, router-dom is now a peer dependency. See above solutions for a resolution.

@bandichandu
Copy link

bandichandu commented Sep 11, 2024

Hi Is there any sample on how to implement the same with react RouterProvider() especially regarding where to place the <SecureRoute /> as all the routes are directly passed to the route provider

export default function App() {
  return <RouterProvider router={router} />;
}

Is there any suggestions on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.