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

Add new API for react-router 6 #274

Closed
wants to merge 38 commits into from
Closed

Conversation

denysoblohin-okta
Copy link
Contributor

@denysoblohin-okta denysoblohin-okta commented Feb 19, 2024

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Adding Tests
  • Build related changes
  • CI related changes
  • Documentation changes
  • Other... Please describe:

What is the current behavior?

Issue Number: OKTA-676780

Resolves #178

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Reviewers

@denysoblohin-okta denysoblohin-okta marked this pull request as ready for review February 22, 2024 16:07
"react-dom": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"optionalDependencies": {
"react-router-dom": ">=5.1.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think react-router-dom is actually an "optional peer dependency" (not an optional dependency)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a minimum version of react-router-dom 6.x we should specify here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to decouple okta-react from react-router.
See my comment: #274 (comment)
What if developer wants to use @remix-run/react router, or Next.js router, reach-router?
Then he would not want a react-router to be installed in node_modules.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left react-router-dom in optionalDependencies just for legacy support of react-router 5

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, but a optional dependencies and a peer dependencies and a optional peer dependencies are not the same thing.

Optional dependency: A dependency that is may or may not be included (optional) to use a package. In the SIW we used to use a async library called fibers to improved the compile time of sass (https://github.com/okta/okta-signin-widget/pull/2863/files)

Peer dependency: A dependency which the application and library need a common version/instance of. For example okta-react need a common instance of React (and authjs). The application consuming okta-react needs React and okta-react needs React AND it needs to be the same "instance" of React

Optional Peer Dependency: Same as a peer dependency, however including the peer dependency is optional

Optional Peer Dependencies are defined as so:

"peerDependencies": {
    "@okta/okta-auth-js": "^7.x"
  },
  "peerDependenciesMeta": {
    "@okta/okta-auth-js": {
      "optional": true
    }
  },

@@ -10,6 +10,9 @@ const makeExternalPredicate = () => {
const externalArr = [
...Object.keys(pkg.peerDependencies || {}),
...Object.keys(pkg.dependencies || {}),
// for SecureRoute:
...Object.keys(pkg.optionalDependencies || {}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed (see package.json comment)

"types": "./dist/bundles/types/react-router-5.d.ts",
"import": "./dist/bundles/okta-react-router-5.esm.js",
"require": "./dist/bundles/okta-react-router-5.cjs.js"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we export a react-router-6 bundle as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could export a react-router-6 bundle with one component called like SecureOutlet that just renders <AuthRequired><Outlet /></AuthRequired>
Probably we should not name it SecureRoute as in RR5, because it's not a route.


const useHashPathForLoginCalback = config.oidc.responseMode === 'fragment' || !config.oidc.pkce;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you rename this variable, use is usually reversed for hooks in React

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to usingFragmentForLoginCalback

src/hooks/useComponents.tsx Outdated Show resolved Hide resolved
src/hooks/useLoginCallbackCheck.tsx Outdated Show resolved Hide resolved
): null => {
// We don't want any side effects (excess render, effect run) on `restoreOriginalUri` change,
// so keep and use the latest value from the ref
const restoreOriginalUriRef = React.useRef(restoreOriginalUri);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is a ref needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a safe way to eliminate Two custom restoreOriginalUri callbacks are detected warning issues.
When another instance of restoreOriginalUri is passed to Security, the effect would not be triggered.
Using empty dependencies array also works, but gives lint error after adding react-hooks/rules-of-hooks to ESlint config

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that linter is used to warn to you to the possibility of a memory leak, it does not mean there is one. It's correct to pass an empty array for this case (we only want the check to fire once on component mount)

The gen3 widget does this as well:
https://github.com/okta/okta-signin-widget/blob/master/src/v3/src/components/Widget/index.tsx#L147

export const waitForAuthenticated = (
oktaAuth: OktaAuth
): Promise<boolean> => {
return new Promise((resolve) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to use await oktaAuth.isAuthenticated() here, rather than the authState

Copy link
Contributor Author

@denysoblohin-okta denysoblohin-okta Feb 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oktaAuth.isAuthenticated() can't be used for this purpose (purpose is to use support react-router's loader).
oktaAuth.isAuthenticated() will return promise that will immediately be resolved with true/false rather that actually waiting for auth state with isAuthenticated: true

(Maybe it's better to delete waitForAuthenticated util if it's confusing, and not support loader)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oktaAuth.isAuthenticated() will return promise that will immediately be resolved with true/false rather that actually waiting for auth state with isAuthenticated: true

I'm not sure I follow this. oktaAuth.isAuthenticated will attempt to read the tokens from the tokenManager and determine their validity (and attempt a renew) before resolving

test/jest/package.json Outdated Show resolved Hide resolved
@denysoblohin-okta
Copy link
Contributor Author

Closing in favour of #275

"react-dom": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"optionalDependencies": {
"react-router-dom": ">=5.1.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, but a optional dependencies and a peer dependencies and a optional peer dependencies are not the same thing.

Optional dependency: A dependency that is may or may not be included (optional) to use a package. In the SIW we used to use a async library called fibers to improved the compile time of sass (https://github.com/okta/okta-signin-widget/pull/2863/files)

Peer dependency: A dependency which the application and library need a common version/instance of. For example okta-react need a common instance of React (and authjs). The application consuming okta-react needs React and okta-react needs React AND it needs to be the same "instance" of React

Optional Peer Dependency: Same as a peer dependency, however including the peer dependency is optional

Optional Peer Dependencies are defined as so:

"peerDependencies": {
    "@okta/okta-auth-js": "^7.x"
  },
  "peerDependenciesMeta": {
    "@okta/okta-auth-js": {
      "optional": true
    }
  },


if (!isLoginRedirect) {
if (children) {
return (<>{children}</>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can provide samples for HashRouters, but I don't think we need direct API support. I don't think it's worth adding complexity to the API

]);

if (handleLoginError) {
return <ErrorReporter error={handleLoginError} />;
return <ErrorReporter error={handleLoginError} />
} else if (!authState?.isAuthenticated) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can you flip this boolean, so the default case renders <Loader />

else if (authState?.isAuthentcated) {
  return <ReactRouterDom.Route {....routeProps } />
}
else {
  return Loading;
}

loginError: Error | null;
}

const useAuthRequired = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they will be similar, but I think the implementations will different slightly

): null => {
// We don't want any side effects (excess render, effect run) on `restoreOriginalUri` change,
// so keep and use the latest value from the ref
const restoreOriginalUriRef = React.useRef(restoreOriginalUri);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that linter is used to warn to you to the possibility of a memory leak, it does not mean there is one. It's correct to pass an empty array for this case (we only want the check to fire once on component mount)

The gen3 widget does this as well:
https://github.com/okta/okta-signin-widget/blob/master/src/v3/src/components/Widget/index.tsx#L147

export const waitForAuthenticated = (
oktaAuth: OktaAuth
): Promise<boolean> => {
return new Promise((resolve) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oktaAuth.isAuthenticated() will return promise that will immediately be resolved with true/false rather that actually waiting for auth state with isAuthenticated: true

I'm not sure I follow this. oktaAuth.isAuthenticated will attempt to read the tokens from the tokenManager and determine their validity (and attempt a renew) before resolving

} else if (!isAuthenticated) {
return Loading;
} else {
return <>{children}</>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your idea of calling this SecureOutlet, let's forward this that

However this component should not render children and should instead return <Outlet />

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