auth0 parseHash can't create property '__enableIdPInitiatedLogin' on hash string - reactjs

I'm trying to upgrade my React web app from auth0-js 9.6.1 to 9.7.3. After installing the new library, my Slack login flow no longer works, it appears to be breaking in the callback.
TypeError: Cannot create property '__enableIdPInitiatedLogin' on string '#access_token={token string}&token_type=Bearer&state={state string}'
My parseHash call is:
this.auth0.parseHash(hash, (err, authResult) => {
if (authResult && authResult.idToken) {
AuthService.setToken(authResult.idToken); // JWT returned from Auth0;
// Redirect user to content.
const returnUrl = localStorage.getItem(Variables.RETURN_URL_KEY);
localStorage.removeItem(Variables.RETURN_URL_KEY);
returnUrl
? window.location.replace(returnUrl)
: window.location.replace("/");
} else if (err) {
console.log("Error with auth callback", err);
window.location.replace("https://foo.com"); // If auth fails, send user to home page.
}
}
This works fine in 9.6.1, but fails in 9.7.x and I can't find anything about any breaking changes that would cause it to start failing. Any ideas?

I had the same issue as you so I opened a ticket on the Auth0.js library github page.
This is the response I got from the developers:
It was working by accident then (also, the string is being ignored in your case), considering that we expect the first parameter to either be an object or a callback function.
All of our docs mention that:
https://github.com/auth0/auth0.js#api
https://auth0.github.io/auth0.js/global.html#parseHash
https://auth0.com/docs/libraries/auth0js/v9#extract-the-authresult-and-get-user-info
In your case, the simplest fix is to just remove the first parameter and keep only the callback. window.location.hash is already used when there's no options object.
(emphasis on the fix mine)
I tested 9.7.3 with this.auth.auth0.parseHash((err, result) => ... and it worked like a charm.
I hope this'll help!

Related

Handling OAuth with React 18 useEffect hook running twice

Background
I have recently upgraded a fairly sizeable React app to React 18 and for the most part it has been great. One of the key changes is the new double mount in development causing useEffect hooks to all run twice, this is clearly documented in their docs.
I have read their new effect documentation https://beta.reactjs.org/learn/lifecycle-of-reactive-effects and although it is quite detailed there is a use case I believe I have found which is not very well covered.
The issue
Essentially the issue I have run into is I am implementing OAuth integration with a third-party product. The flow:
-> User clicks create integration -> Redirect to product login -> Gets redirected back to our app with authorisation code -> We hit our API to finalise the integration (HTTP POST request)
The problem comes now that the useEffect hook runs twice it means that we would hit this last POST request twice, first one would succeed and the second would fail because the integration is already setup.
This is not potentially a major issue but the user would see an error message even though the request worked and just feels like a bad pattern.
Considered solutions
Refactoring to use a button
I could potentially get the user to click a button on the redirect URL after they have logged into the third-party product. This would work and seems to be what the React guides recommend (Although different use case they suggested - https://beta.reactjs.org/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers).
The problem with this is that the user has already clicked a button to create the integration so it feels like a worse user experience.
Ignore the duplicate API call
This issue is only a problem in development however it is still a bit annoying and feels like an issue I want to explore further
Code setup
I have simplified the code for this example but hopefully this gives a rough idea of how the intended code is meant to function.
const IntegrationRedirect: React.FC = () => {
const navigate = useNavigate();
const organisationIntegrationsService = useOrganisationIntegrationsService();
// Make call on the mount of this component
useEffect(() => {
// Call the method
handleCreateIntegration();
}, []);
const handleCreateIntegration = async (): Promise<void> => {
// Setup request
const request: ICreateIntegration = {
authorisationCode: ''
};
try {
// Make service call
const setupIntegrationResponse = await organisationIntegrationsService.createIntegration(request);
// Handle error
if (setupIntegrationResponse.data.errors) {
throw 'Failed to setup integrations';
}
// Navigate away on success
routes.organisation.integrations.navigate(navigate);
}
catch (error) {
// Handle error
}
};
return ();
};
What I am after
I am after suggestions based on the React 18 changes that would handle this situation, I feel that although this is a little specific/niche it is still a viable use case. It would be good to have a clean way to handle this as OAuth integration is quite a common flow for integration between products.
You can use the useRef() together with useEffect() for a workaround
const effectRan = useRef(false)
useEffect(() => {
if (effectRan.current === false) {
// do the async data fetch here
handleCreateIntegration();
}
//cleanup function
return () => {
effectRan.current = true // this will be set to true on the initial unmount
}
}, []);
This is a workaround suggested by Dave Gray on his youtube channel https://www.youtube.com/watch?v=81faZzp18NM

salesforce forcejs not getting refresh token

i am using forcejs in my angular app which is working fine and gives me accessToken. However, I am not able to get refreshToken to be able to renew accessToken as needed. The code is below
import { OAuth, DataService } from 'forcejs';
async loginSFDC(){
let callbackUrl = 'https://my.callback.url'
let oauth = OAuth.createInstance('client key','', callbackUrl);
oauth.login().then(
async (oauthResult) => {
DataService.createInstance(oauthResult);
console.log("Logged Into Salesforce Successfully:::" + JSON.stringify(oauthResult));
});
}
the above code is printing accessToken but no refreshToken. Please advise
i have also tried passing the 2nd parameter in createInstance as http://login.salesfoce.com?scope=full+refresh_token but that does not work as url gets constructed wrong on adding the scope=full+refresh_token
From looking at the source code of forcejs, you can use the refreshAccessToken() method with the DataService instance you created.
After some more debugging it is discovered that the refresh token shows up when my code is running on localhost but does not when it is deployed to the the webserver. i dont know how to debug further or fix it. but i have verified that this behavior is consistently reproducible

symfony 5 web debug toolbar showing anonymous and cannot redirect after onAuthenticationSuccess

I have been following along with the following Symfony tutorials, but I believe they are using version 4 and I am using version 5. They reach a point in the tutorial which shows that the web debug toolbar shows the user's email logged and they even pointed out that if you see logged as anonymous, then just refresh. I did refresh, but it still shows as anon.
As you can see by the following screen shot, login was successful and it shows the correct username as well:
I started to watch the first part of the tutorial - listed below - when I reached a point in the second part that pointed out that I should watch the first part, which made sense, that I might have missed something, but that was an even older version of Symfony and things have changed in version 5.
First part of the tutorial
Second part of the tutorial
After going through the tutorials, I still have the web debug tool showing anon. Now, I am using React as a form to POST the email and password - see next screen shot - would that effect how the web debug toolbar, but I do not see how, because the console shows that the system knows the user.
Does anyone know a config that needs to be changed?
I have tried changing the following within src\Security\TokenAuthenticator - getUser from:
return $this->em->getRepository(User::class)
->findOneBy(['apiToken' => $credentials])
;
To:
return $this->em->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
But no change, still shows anon
Also, as the subject states, I cannot redirect via onAuthenticationSuccess
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
}
I do not see why this does not work. Again, is it because I am posting via a React app?
Turns that it is because I am running an older version of the browser Firefox and the log in is working. You can see by the screen shot of both Firefox and Chrome, that it is working Chrome
As far as the redirect goes, PHPStorm was saying that I did not have urlGenerator available in the TokenAuthenticator class. As a result, I should have noticed before and this is what I did to correct it:
In my src\Security\TokenAuthenticator I have the following:
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
In my constructor:
private $em;
private $urlGenerator;
public function __construct(EntityManagerInterface $em, UrlGeneratorInterface $urlGenerator)
{
$this->em = $em;
$this->urlGenerator = $urlGenerator;
}
My onAuthenticationSuccess:
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
// redirect to some "app_homepage" route - of wherever you want
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
}
But it is still not working
I have tried
use Symfony\Component\HttpFoundation\RedirectResponse;
private $redirectResponse;
public function __construct(EntityManagerInterface $em, RedirectResponse $redirectResponse)
{
$this->em = $em;
$this->redirectResponse = $redirectResponse;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
// redirect to some "app_homepage" route - of wherever you want
return $this->redirectResponse->redirectToRoute('app_homepage');
}
But PHPStorm tells me that it cannot find method redirectToRoute within class RedirectResponse
The only thing that I have found to work with redirecting users to the home page after successful is login, is within my React login app. I have an async to my handleClick method, after the fetch POST, I have a setTimeout of 3000 that uses a plain javascript:
window.location.href = '/';
I would love to know the answer to why I cannot redirect via the Authenticator class that I have created, but at least someone who is using Firefox will not have to wonder why their web debug tool is not showing that the user has successfully logged in while still showing anon

Meteor redirect client from server-side method

Meteor 1.6, React, React Router
Interfacing with Paypal billing-agreements
Client onClick event:
subPayPal(){
Meteor.call('paypal.getToken', this.props.user_id, (error, response) => {
if(error) {
console.warn(error);
}else{
console.log(response);
}
}
}
I'm calling the method on the server rather than on the client because I'm dealing with privileged info (usernames, passwords, tokens, etc). No problem here.
Server methods:
Meteor.methods({
'paypal.getToken': function getOauthToken(uid){
// simplified a bit
// check if current token valid
// set vars here, then go get token
axios.post(ppAPI, data, config)
.then( function(response) {
// save oAuth token
})
.catch( function(error){
// stuff here
})
// prepare another axios.post(), with oauth Token, to get a payment token
// and approval_url and execute_url from paypal
// call axios.post() and use data in response:
// with the approval_url in this reponse, I need to redirect
// the browser to the approval_url on paypal.com so that the user
// can sign into paypal, and ok the subscription agreement.
// once user 'ok' in paypal, the browser comes back to my site,
// where I render a 'cart' with a final 'ok, purchase' button.
return approval_url;
}
})
So, once I have the approval_url, I can send it back to the client, and when the client "sees" it, it can then call the React Router to the paypal.com site.
PROBLEM
The client's onClick method is obviously async and as soon as I click the initial onClick(), the console.log outputs undefined for process which makes perfect sense.
TRIAL 1
So, I tried using Meteor.apply in the client trying to make it synchronous, waiting for the server-side method to return the approval_url to no avail:
subPayPal(){
Meteor.apply('paypal.getToken', [{uid:user_id}], {
onResultReceived: (error, response) => {
if(error) console.warn(error.reason);
if(response) console.log('server response', response);
}
});
}
I also tried Meteor.call('paypal.getToken').then({ console.log(response) }).catch... to no avail either, as it is still async.
I've also tried try/catch blocks in the server-side method, to no avail. Whatever I try, the server-side code always runs as expected (with exception of the Meteor.call().then().catch() which just plain failed). I just can't seem to promise it, or return it.
TRIAL 2
The next thought would be not caring about the response on the client, if I could get the server-side method, with approval_url defined, to somehow call the React Router on the client and push the unique approval_url to it, but that doesn't make much sense to me how to wire that up.
THOUGHT 1
I guess I could use some "temporary" database collection, which is reactive, so that when the server-side method completes, it'll update (or insert/create) a document, and when the client 'sees' that, it could then call the React Router to redirect the browser to paypal.com. The collection document would have to hold the approval_url URI, which would then be passed down to the client. Not too sure how I'd wire the client to tell the Router when it sees the approval_url "appear".
Sooooo....
Any ideas? Is there more than one solution (and if so, what would be the best?).
I read somewhere that the app should logout the user, then the Router could redirect to paypal, but that doesn't help, as paypal.com redirects back to me, and I wouldn't want the user to have to log back in again.
THANKS.

MVC6 Prevent Redirect on unauthorized

I'm developing an ASP.NET MVC 6 Web API app, with AngularJs frontend.
When I leave a session to decade, or I'm trying to call a Web API action unauthorized, I expect to receive a 401 status code.
Instead, I get a 302, and tries to redirect to the default path for login ("/Account/Login").
So I need to handle this in Angular.
From other forum posts here and googling I found that some people resolved their problems using in startup.cs:
services.Configure<CookieAuthenticationOptions>(options =>
{
options.LoginPath = PathString.Empty;
});
No luck for me.
I use Identity as authentication backend and even adding
services.ConfigureIdentityApplicationCookie(options =>
{
options.LoginPath = PathString.Empty;
});
does not give me the expected result. ASP.NET docs suggest this way to return a 401.
Using 1.0.0-beta7 CLR x86, IIS Express.
EDIT: the solution proposed by #EZI is correct.
Below my answer, which doesn't work on recent release.
Finally! I found the solution!
To be complete, I started with this comment found on source code in aspnet/Identity github.
// If the LoginPath is null or empty, the middleware will not look for 401 Unauthorized status codes, and it will not redirect automatically when a login occurs.
which give me the wrong directions.
Digging with debug on ConfigureIdentityApplicationCookie' options, I found that there is a delegate on "Notifications" property
OnApplyRedirect
Bingo!
Now I can control the redirect.
services.ConfigureIdentityApplicationCookie(options =>
{
options.LoginPath = PathString.Empty;
options.Notifications = new CookieAuthenticationNotifications {
OnApplyRedirect = context => { context.Response.StatusCode = 401; }
};
});
This maybe isn't a good way to handle the problem, but finally I receive a 401 Unauthorized when the web.api action is called without authentication.
For me it worked to just set the AutometicAuthenticate to false.
services.Configure<IdentityOptions>(options =>
{
options.Cookies.ApplicationCookie.AutomaticAuthenticate = false;
options.Cookies.ApplicationCookie.AutomaticChallenge = false;
options.Cookies.ApplicationCookie.LoginPath = PathString.Empty;
});
my solution was similar to #Ezi
Confirmed working for RC2
services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
options.Cookies.ApplicationCookie.AutomaticChallenge = false;
});

Resources