okta / okta-auth-js

The official js wrapper around Okta's auth API
Other
455 stars 267 forks source link

IE Callback Redirection Not Working (Intermittent Issue) With Latest okta-auth-js SDK #601

Open MultiPlanJSEval opened 3 years ago

MultiPlanJSEval commented 3 years ago

With new okta-auth-js code, we are having an intermittent issue in IE with callback handler where after okta redirects, user is stuck with a blank page with URL http://localhost:port/implicit/callback. image

AppWithRouterAccess.js

import React from 'react'; import { Route, useHistory } from 'react-router-dom'; import { OktaAuth } from '@okta/okta-auth-js'; import { Security, LoginCallback } from '@okta/okta-react'; import SignIn from './SignIn'; import Home from './Home';

const AppWithRouterAccess = (props) => { const history = useHistory(); let appID = localStorage.getItem('appID')? localStorage.getItem('appID'):'employee'; let relocation = '';

if (appID) { relocation = localStorage.getItem('appRedirectURL'); }

const onAuthRequired = () => { history.push('/login'); }; let clientConfig = {  issuer :"https://{{your_okta_org}}/oauth2/default",         clientId:"{{your_app_client_id}}",         pkce:"true",     disableHttpsCheck:"true",     tokenManager: {storage: "sessionStorage"},  redirectUri: window.location.origin + '/implicit/callback', onAuthRequired: onAuthRequired};
const oktaAuth = new OktaAuth(clientConfig);

return (

<Security  oktaAuth={oktaAuth}>

  <Route path='/login' render={() => <SignIn />} />          
  <Route exact path="/" render={() => <Home />} />
  <Route path='/implicit/callback' component={LoginCallback} />
</Security>

); }; export default AppWithRouterAccess;

Below code for authentication response

  oktaAuth.signInWithCredentials({ username, password })
    .then(res => {
      const sessionToken = res.sessionToken;
      setSessionToken(sessionToken);
      // sessionToken is a one-use token, so make sure this is only called once
      oktaAuth.signInWithRedirect({ sessionToken });
    })
    .catch(err => {
      console.log('Found an error', err)
      setError(err.errorSummary);
    });
aarongranick-okta commented 3 years ago

Hi @MultiPlanJSEval,

We are aware of an issue with the LoginCallback component in the okta-react SDK, where it does not properly handle the error and error_description when it is returned on the login redirect URI. You can check your network history to see if there was an "error" parameter on the callback. (AuthJS will remove these from the location).

We are working on fixing this issue in okta-react. Our advice for workaround is to copy the LoginCallback to your own source tree, where it can be customized. You should be able to catch the error by adding a try {} catch around handleLoginRedirect

Related issues: https://github.com/okta/okta-oidc-js/issues/926 https://github.com/okta/okta-react/issues/76

internal ref: OKTA-361608

MultiPlanJSEval commented 3 years ago

Hello,

https://github.com/okta/okta-react/blob/master/src/LoginCallback.tsx is TSX format. Can we get JS version to avoid below compilation error.

TypeScript error in C:/Okta_project/MultiPlanLoginPortal/src/views/LoginCallback.tsx(1,1): 'LoginCallback.tsx' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module. TS1

Also, this resolution seems to be to intercept the error but do we know why we are getting this error even after successful authentication in Okta. Having this error reporter will be more frustration than solving the root cause (do we know?).

Thanks

Pavan

aarongranick-okta commented 3 years ago

@MultiPlanJSEval The only thing the callback must do is call handleLoginRedirect exactly once https://github.com/okta/okta-react/blob/master/src/LoginCallback.tsx#L26

As for typescript, you should be able to change the file to .jsx extension and make only small changes (removing type information).

There are errors that can occur after successful authentication. Authentication means you have a valid user account and the password matches and you can login to your Okta homepage. However, to get tokens for an OIDC app the user must also be assigned to that application. This is the most common error we see.

Another possibility is that the token exchange failed for technical reasons. This is more common on IE where a polyfill may be needed. https://github.com/okta/okta-auth-js#browser-compatibility--polyfill

You will want to examine the error thrown from handleLoginRedirect to see what is happening in this case.

MultiPlanJSEval commented 3 years ago

Recommended changes did not work initially. There was no error caught inside try-catch. But after changing the return of the LoginCallback.js from null to  return  window.location.href = <>; it is working in IE as expected. We are not sure if this is the right approach to use own custom LoginCallback.js with return value changed. Please let us know if this workaround is part of best practices for okta-auth-js.

aarongranick-okta commented 3 years ago

@MultiPlanJSEval If the condition is intermittent, then I suspect it is a race condition. I noticed in the block of code posted:

 oktaAuth.signInWithCredentials({ username, password })
    .then(res => {
      const sessionToken = res.sessionToken;
      setSessionToken(sessionToken);
      // sessionToken is a one-use token, so make sure this is only called once
      oktaAuth.signInWithRedirect({ sessionToken });
    })
    .catch(err => {
      console.log('Found an error', err)
      setError(err.errorSummary);
    });

This line: oktaAuth.signInWithRedirect({ sessionToken }); does not have a return statement preceding this. This means that the promise chain will be broken and signInWithRedirect will be executing disconnected in async time/space.

Any other code executing while the redirect is being prepared may tamper with storage used by the auth flow.

There must be only one auth flow executing at a time (this includes token renew).

You should be able to see the exact error by putting a block like this in your LoginCallback component:

handleLoginRedirect.catch(e => {
  console.error('caught error', e);
});
MultiPlanJSEval commented 3 years ago

We are using same code snippet from https://developer.okta.com/code/react/okta_react/ where there is no return statement before line oktaAuth.signInWithRedirect({ sessionToken }); Even if we add a return statement before this line, I am not sure hwo the code will execute the line. It would be nice to see an example posted here for us to try the recommended solution.

We also added try-catch in LoginCallback.js (code snippet below)

import * as React from 'react';
--
import { useOktaAuth} from  '@okta/okta-react';
 
 
const LoginCallback = ({ errorComponent }) => {
const { oktaAuth, authState } = useOktaAuth();
const authStateReady = !authState.isPending;
 
//const ErrorReporter = errorComponent ;
let errorrepo ='';
let relocation = localStorage.getItem('appRedirectURL');
React.useEffect(() => {
try{
 
oktaAuth.handleLoginRedirect();
 
}
catch(error){
errorrepo = error;
console.log(error);
}
 
}, [oktaAuth]);
 
if(authStateReady && authState.error) {
return <div>{authState.error}</div>;
}
 
return  window.location.href = relocation;
};
 
export default LoginCallback;

Thanks

Pavan and Sudheer