Closed troyblank closed 2 years ago
Hi @troyblank,
This is usually down to some misconfiguration on the Auth0 app side. Does this FAQ item help you any?
@stevehobbsdev ,
Thanks for the information but unfortunately I don't think my issue is with the Auth0Client
. I did double check everything in the FAQ and I appear to be in the green in that regard.
So to demonstrate where everything is breaking down here is a modification to my original example with a few "timestamped" logs that show Auth0Client
is always fetched quickly but the getTokenSilently
is what takes a very long time to report error.
export const attemptToGetAuthToken = async(): Promise<AuthType> => new Promise( ( resolve, reject ) => {
console.log( 'Started to look for tokens', new Date() )
const urls = getUrls()
configureAuth0Client().then( ( auth0Client ) => {
console.log( 'Received Auth0 client', new Date() )
auth0Client.getTokenSilently( {
audience: urls.api,
scope: 'openid profile email',
redirect_uri: urls.properties.login,
leeway: 300,
} ).then( ( jwt ) => {
// This gets called when there is a valid token to fetch in a timely fashion.
resolve( parseJWT( jwt ) )
} ).catch( ( { error } ) => {
// This always gets called after 60 seconds (default timeoutInSeconds) if there is valid token to fetch.
console.log( 'getTokenSilently finally errors with timeout', new Date() )
reject( new Error( error ) )
} )
} )
} )
and the output
Started to look for tokens Mon Oct 04 2021 08:51:29 GMT-0500 (Central Daylight Time)
Received Auth0 client Mon Oct 04 2021 08:51:29 GMT-0500 (Central Daylight Time)
getTokenSilently finally errors with timeout Mon Oct 04 2021 08:52:46 GMT-0500 (Central Daylight Time)
As you can see it takes 60 seconds to error out, which is the default timeoutInSeconds
. When a user first hits the app and has yet to login, I want to be able to redirect them to a login page if they have no authentication. By using getTokenSilently
only the user has to wait 60 seconds before I can determine they are not authenticated.
Also if it helps here is the response I am getting back from /authorize
as soon as getTokenSilently
is called.
<!DOCTYPE html><html><head><title>Authorization Response</title></head><body><script type="text/javascript">(function(window, document) {var targetOrigin = "http://localhost:8020";var webMessageRequest = {};var authorizationResponse = {type: "authorization_response",response: {"code":"REDACTED","state":"REDACTED"}};var mainWin = (window.opener) ? window.opener : window.parent;if (webMessageRequest["web_message_uri"] && webMessageRequest["web_message_target"]) {window.addEventListener("message", function(evt) {if (evt.origin != targetOrigin)return;switch (evt.data.type) {case "relay_response":var messageTargetWindow = evt.source.frames[webMessageRequest["web_message_target"]];if (messageTargetWindow) {messageTargetWindow.postMessage(authorizationResponse, webMessageRequest["web_message_uri"]);window.close();}break;}});mainWin.postMessage({type: "relay_request"}, targetOrigin);} else {mainWin.postMessage(authorizationResponse, targetOrigin);}})(this, this.document);</script></body></html>
It usually takes less than a second for me. What is strange is this response does nothing for the getTokenSilently
promise. I figured as soon as /authorize
gets a response so should getTokenSIlently
.
Thanks @troyblank. This is definitely not normal and I'm unable to reproduce it with my tenant and the playground. You mention it's unreprodible there, but you can configure the switches at the bottom - if you turn off "Use Auth0Client Constructor", then createAuth0Client
will be called and then you can press the "Get Token" button to call getTokenSilently
. What should happen is that the call should return very quickly with a login_required
error.
Things to try, to rule out a configuration problem:
https://user-images.githubusercontent.com/766403/135995959-b181efee-c285-4d1f-aa87-7fb15b95120e.mov
If you're not getting anywhere with the above, there are other things we could try with the code you've mentioned above. I've put some comments inline to make it easier to see:
import createAuth0Client, { Auth0Client } from '@auth0/auth0-spa-js'
export const configureAuth0Client = async(): Promise<Auth0Client> => {
return await createAuth0Client( {
domain: 'REDACTED',
client_id: 'REDACTED',
audience: 'REDACTED',
leeway: 300,
} )
}
export const attemptToGetAuthToken = async(): Promise<AuthType> => new Promise( ( resolve, reject ) => {
const urls = getUrls()
configureAuth0Client().then( ( auth0Client ) => {
auth0Client.getTokenSilently( {
audience: 'REDACTED', // If this is the same as in `createAuth0Client` above, this can be omitted
scope: 'openid profile email', // This is the same value as the default and can be omitted
redirect_uri: 'REDACTED', // This should be specified in createAuth0Client
leeway: 300,
} ).then( ( jwt ) => {
resolve( parseJWT( jwt ) )
} ).catch( ( error ) => {
console.log(error)
reject( new Error( error.error ) )
} )
} )
} )
Although some of these things might seem innocuous, they could be affecting the experience and would be good to understand that in case we need to do something to fix it. I'd also like to understand what led you to configuring the SDK in this way and whether we could do a better job of explaining the best set up.
Give those steps a try and let me know how you get on.
@stevehobbsdev Thanks for these insights. I tried the commented suggestions but still get the timeout
.
I think I am closer at figuring out why this timesout. My understanding of how getTokenSilently
is probably way off. I assumed when /authorize
returned a status thegetTokenSilently
promise would also respond. But looking at this line inside @auth0/auth0-spa-js
it appears the iframe is never cleaned up and thus no message is ever sent. I find this frustrating because I am not sure why at this point you would not want to communicate back to the invoker.
This is further confirmed when converting to React
and using @auth0/auth0-react
. These timeouts still occur (by not getting an error till 1.5 mins in) as it uses @auth0/auth0-spa-js
under the hood. The errors that take 1.5 seconds do go away until when wrapping the component that is using useAuth0
with a withAuthenticationRequired
. This forces the user to redirect to whatever the redirect uri setting is. It is at this point I realized this is probably the intended use case.
My failing of understand is this library really does not want you to handle any places where the user does not have a token or is not authenticated. I feel it really does expect you to rely on a setup that allows auth0 to push the user to the redirect_uri
before the inevitable timeout
occurs. I feel the friction I was seeing in this issue was my app could not rely on getTokenSilently
alone, I need other tools such as withAuthenticationRequired
so that auth0 forces the user to a login page before the timeout error occurs.
If there was any suggestion I would make to improve this library would be to not just return
in iframeEventHandler
on this line and instead at least provide a a response back so the invoker knows something is wrong. Obviously this is spoken from a fairly narrow perspective so I would love to hear feedback on if that is a good idea or not.
Unfortunately, the web_message
flow (which is what it's doing by default when you call getTokenSilently
) relies on Auth0 server returning an HTML page that contains some code that calls postMessage
, which the SDK responds to. The timeout is there in the event that Auth0 returns something (like an error page) that doesn't invoke the right thing. This happens to be the case when your Auth0 tenant is misconfigured, usually if you haven't used the correct "Allowed Web Origins" value. You mentioned that this was all configured correctly, so I'm not sure what the case may be at this point. And the HTML snippet you posted earlier looks healthy.
You should be able to do what you're trying to do: call getTokenSilently
to figure out whether the user is logged in or not and get a login_required
error as my video shows, without a timeout.
I tried the commented suggestions but still get the timeout.
Do you still get the timeout even when using the brucke.auth0.com
tenant as defaulted in the Playground?
If there was any suggestion I would make to improve this library would be to not just return in iframeEventHandler on this line
I think that's a good suggestion and will investigate that further 👍🏻
As stated in my first post the playground will never hit this error because before it calls getTokenSilently
it does this check and thus getTokenSilently
is never called without already having authentication:
if (!this.cookieStorage.get(this.isAuthenticatedCookieName))
My issue is getTokenSilently
times out when there IS NO token or authentication.
It is possible (and I would bet) that removing this check and a few other modifications will show the timeout
issue.
I feel like we're going around in circles - I mentioned that with the playground, you're able to actually hit a button that explicitly calls getTokenSilently
and you can do this without being logged in, as shown in the video. getTokenSilently
itself does not check for this cookie.
I do apologise if I'm missing something but I'm not sure how to explain it any clearer.
It is possible (and I would bet) that removing this check and a few other modifications will show the timeout issue.
I've removed the check in the playground environment and it does not show a timeout issue, but I can't reproduce your issue as it is, which is why I'm leaning so hard on a config issue to begin with; I don't feel like we've totally ruled it out yet.
I think to progress any further I'm going to need a small, isolated reproducible app from yourself that demonstrates the timeout. What would be helpful is if you can also supply the domain and client ID you're using, if you're happy to do so. If you'd prefer that not to be public, feel free to DM me in the Auth0 Community Forums with the information there. Also please test your repro with the domain and client ID used in the playground (it's set up to work on an app at http://localhost:3000
) to verify that you're seeing the timeout there as well.
My apologies I do see getTokenSilently
running per your video. The playground might be a bit to dense for me to figure out what Is going wrong on my end, and I am struggling to see a 1 to 1 ratio of getTokenSIlently
in the playground to the one I am hitting while developing.
Here is a repo with an example https://github.com/troyblank/getTokenSilently
If you run this project you will see it takes 1.5 mins to get a response from getTokenSilently
. Please take a look and tell me what you think.
No worries, we'll get to the bottom of it 💪🏻
I am struggling to see a 1 to 1 ratio of getTokenSIlently in the playground
Really the scenario we're testing is being able to call getTokenSilently
when the user is not logged in, I think that's what the problem reduces to here. And you should 100% be able to do that.
I've checked out the sample and recorded a debug session. I did hit a timeout error initially when I loaded it, and it was because the client that it's configured with on brucke.auth0.com
does not have http://localhost:9000
listed in its Allowed Callback URLs setting. That client uses http://localhost:3000
as its callback. So I reconfigured the sample you made to run on port 3000 instead and without any other code changes, it works fine.
I've attached the video here. You'll see me load it initially, then stop and reconfigure the port after checking out the network trace. Then I can reload the app and I get "Auth is done checking" on the screen after a few ms.
https://user-images.githubusercontent.com/766403/136089684-49c12565-1c3d-4b6e-be88-39259fe25cf8.mov
This still feels to me like a config problem so far; the next thing I would do is double - and triple - check the fields for the Auth0 client in your Auth0 dashboard, and ensure that Allowed Redirect URLs and Allowed Web Origins have the correct values.
AH! cool thanks. So yes this has to be on the tenant side then 🤔. I am not off to figure out what the issue is there. In all transparency when I started this I did not have access to edit the client to test things out. It wasn't till I was complaining about this error a ton that I did get access 😂.
I looked at the client's config and all looked good to me. I had my localhost and port in Allowed Callback URLs
as well as Allowed Web Origins
. I did notice it was not in Allowed Origins (CORS)
though. So I added it there and saved. The callback would not timeout anymore, hooray!.
I still think this experience could be improved upon though, the error is still vague to me even though we have pin-pointed it to be a tenant issue. Particularly with poor people like me without admin keys :)
Thanks again!!
Great! Glad you've managed to resolve it. I definitely agree it can be improved upon, with this particular thing we're kind of at the whim of what the server is returning to us but I'll take that feedback on board and see what we can do 👍🏻
I believe we could probably add a little section to the readme that mentions the URL configuration just as is mentioned on https://auth0.com/docs/quickstart/spa/vanillajs.
I'll borrow this one from nextjs-auth0 and tweak it a bit 👍🏻
Describe the problem
When using
getTokenSilently
the promise will take whatever time is set withtimeoutInSeconds
and then give the error"timeout"
when the user does not have a valid token. This is not ideal because I do not want the user to wait a long time before I can determine they do not have a valid token and send them to the login page.What was the expected behavior?
For
getTokenSilently
to error as soon as call to/authorize
finishes and not timeout.Reproduction
First make sure the user has no previous authentication. I do this by usually using a new incognito window in chrome. Then call
attemptToGetAuthToken
anywhere in the application using the code below.The error that is logged out in the console is
Please note the auth0 logs in this scenario are all green.
Also the call locally to
/authorize
when the timeout occurs returns with a200
status in usually less than 2 seconds. I would figure this return would tellgetTokenSilently
to respond in some fashion but it does not appear to be the case. The return is an html page with the title "Authorization Response".Can the behavior be reproduced using the SPA SDK Playground?
It can not, although I did notice the
checkSession
never allowsgetTokenSilently
to be called in this playground because it checks for a cookie first.I am attempting to use
getTokenSilently
without checking any perviously stored cookies. Maybe this is bad form?Environment
auth0-spa-js
used: 1.18.0