-
Notifications
You must be signed in to change notification settings - Fork 350
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
Cannot automate login in cypress since 1.12 using cookie injection #581
Comments
@eliasplatekdkt thanks for raising. In v1.12 we moved from using cookies for the transaction store to session storage, so if you're relying on cookie behaviour your tests will need to be updated. It does get slightly easier though, in that we no longer include the state value in the key name, so that can be dropped. Otherwise, the data stored inside session storage should be exactly the same as it was for cookies. Let me know if you're able to get this working as before. |
Hi Steve, thanks for pointing me in the right direction, I've made some changes to my code in order to use the session storage. const { access_token, expires_in, id_token } = body
cy.server()
cy.route({
url: Cypress.env("AUTH0_GET_TOKEN_URL"),
method: "POST",
response: {
access_token,
id_token,
scope: "openid profile email",
expires_in,
token_type: "Bearer",
},
})
cy.window().then((win) => {
win.sessionStorage.setItem(
"a0.spajs.txs",
JSON.stringify({
nonce: "how can i set a valid nonce here ?",
code_verifier: "code_verifierIn",
appState: appState,
scope: "openid profile email",
audience: Cypress.env("AUTH0_AUDIENCE"),
redirect_uri: "http://localhost:3000",
})
)
})
cy.visit("/?code=test-code&state=state") But I still have a problem that I don't know how to solve, when I'm calling the POST /oauth/token, the Do you have any idea how I can get a nonce in my API call ? |
Normally would specify the |
I'm not using the I wrote this code based on what is still recommended on the official documentation: https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/ The limitation of cypress is that I cannot navigate to another domain than the domain under test. So I don't see a way to automate the login without using The initial solution would still work if the library does not try to validate the nonce when it is not defined in the id_token, would that be an acceptable solution ? |
@stevehobbsdev I've been playing a bit with the library, and it can be fixed by removing the pre-check on the nonce in handleRedirectCalback: // Transaction should have a `code_verifier` to do PKCE and a `nonce` for CSRF protection
if (!transaction || !transaction.code_verifier || !transaction.nonce) {
throw new Error('Invalid state');
} This check can be removed safely I think because the nonce is revalidated after getting the token in verifyIdToken: if (options.nonce) {
if (!decoded.claims.nonce) {
throw new Error(
'Nonce (nonce) claim must be a string present in the ID token'
);
}
if (decoded.claims.nonce !== options.nonce) {
throw new Error(
`Nonce (nonce) claim mismatch in the ID token; expected "${options.nonce}", found "${decoded.claims.nonce}"`
);
}
} If the transaction test only checks for the code_verifier before calling the From my point of view it seems enough to fix it like this, I can provide a PR if it's okay for you |
@eliasplatekdkt Good spot, this actually looks like a regression as we only started checking @adamjmcgrath what are your thoughts here? I understand we should only be checking |
The login action started failing after upgrading to 1.12.0, I didn't change anything except upgrading the library. BTW here is the configuration I'm using: export const auth0Config: Auth0ClientOptions = {
domain: REACT_APP_AUTH0_DOMAIN!,
client_id: REACT_APP_AUTH0_CLIENTID!,
redirect_uri: window.location.origin + "/callback",
audience: REACT_APP_AUTH0_AUDIENCE,
useRefreshTokens: true,
} I cannot use my code anymore because I was setting the transaction data in the cookie as it was originally working. After the upgrade, the transaction data is defined in the session storage as you told me in your comment, so updating the transaction setup in the storage is working fine, here is the complete version of the login command: Cypress.Commands.add("login", ({ username, password }, appState = { targetUrl: "/" }) => {
cy.log(`Log in as ${username}`)
// Call the Auth0 API to get a token for the provided username with
// with the grant_type password to https://{your-tenant}.auth0.com/oauth/token
cy.request({
method: "POST",
url: Cypress.env("AUTH0_GET_TOKEN_URL"),
body: {
grant_type: "password",
username,
password,
audience: Cypress.env("AUTH0_AUDIENCE"),
scope: "openid profile email",
client_id: Cypress.env("AUTH0_CLIENTID"),
client_secret: Cypress.env("AUTH0_CLIENT_SECRET"),
},
}).then(({ body }) => {
// body contains the result of the call to Auth0 API
const { access_token, expires_in, id_token } = body
// Mocking the call to the Auth0 API that will be fired
// by the handleRedirectCallback action in the library,
// and injecting the token from the previous call
cy.server()
cy.route({
url: Cypress.env("AUTH0_GET_TOKEN_URL"),
method: "POST",
response: {
access_token,
id_token,
scope: "openid profile email",
expires_in,
token_type: "Bearer",
},
})
// Injecting the transaction object that will be used by the library
// to setup the call to the Auth0 API (it's supposed to contain the
// code_verifier from the code query param be it won't be used)
cy.window().then((win) => {
win.sessionStorage.setItem(
"a0.spajs.txs",
JSON.stringify({
code_verifier: "any-code",
appState,
scope: "openid profile email",
audience: Cypress.env("AUTH0_AUDIENCE"),
redirect_uri: "http://localhost:3000",
})
)
})
cy.visit("/?code=any-code&state=any-state")
})
}) Although he difference with the library is that I'm using the |
True, you don't need the I'm ok with removing the @eliasplatekdkt - could you avoid this and just get an access token via the api, then store it in localstorage and use It relies less on our internals, so would be less prone to being broken. export const auth0Config: Auth0ClientOptions = {
domain: REACT_APP_AUTH0_DOMAIN!,
client_id: REACT_APP_AUTH0_CLIENTID!,
redirect_uri: window.location.origin + "/callback",
audience: REACT_APP_AUTH0_AUDIENCE,
useRefreshTokens: true,
cacheLocation: window.Cypress ? 'localstorage' : 'memory'
} and cy.window().then((win) => {
win.localStorage.setItem(
`@@auth0spajs@@::${client_id}::${audience}::${scope}`,
JSON.stringify({
access_token,
...
})
)
}) similar to: https://sandrino.dev/blog/writing-cypress-e2e-tests-with-auth0#jamstack-and-single-page-applications |
@adamjmcgrath thanks for the link I didn't know about this way of logging in. If I'm understanding correctly the solution using the localStorage is way more complex, because before using the localStorage some cookies checks are performed, and the implemented solution requires:
Even if this solution seems more interesting because I wouldn't even need to change the app code, the setup is really more complex and doesn't seem to be recommended by the official documentation that says that we shouldn't test the login page, and it relies on internals that can change and break the code the same way that I'm struggling today with the cookie management :) It doesn't seem possible to me to only inject the token in the localStorage, I've tried to log in with my app using for So I tried to inject the cookie and the localStorage key, but the library still tries to redirect to the login page, this time I can't see why. I'm not sure that using memory or localstorage will change the matter here, because in both solutions there is a need to rely on some internals of the library, with that said, if moving the nonce validation further in the code doesn't occur security issues, it seems a more benefical solution to me. |
Hey @eliasplatekdkt
apologies, I just used that as an example of setting I've put a working example here https://github.com/adamjmcgrath/cypress-spa-example I think it's simpler than your solution because it doesn't rely on cy.request/cy.server |
@adamjmcgrath this is a very elegant solution and it works seamlessly. I tried the same implementation but some fields were missing which is why it didn't work. Thanks a lot for your time ! I think that would be interesting to update the documentation at https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/ which explains quite a different approach, and can be misleading for people searching for |
Been debugging this for a few hours now. Glad someone found a working solution. Thank you! |
The implementation seems to work fine for logging in initially but my Cypress suite seems to get stuck whenever a redirect occurs within the app. Spent several hours trying to debug but couldn't really find anything helpful. For now, just reverting back to v1.11.0. |
@eliasplatekdkt @adamjmcgrath Thank you so much guys ! It helps me a lot to auto login users on my SPA ! 💪 |
Glad you got it working @vinzcelavi! |
One thing that tripped me up was I had |
Thanks for sharing that @StephenCleary If anyone else is having trouble setting up #581 (comment) - It's usually because the I added a suggestion and snippet of code that might help you debug situations like this here adamjmcgrath/cypress-spa-example#2 (comment) |
Describe the problem
In order to automate test to a frontend application i'm currently working on, I'm using cypress on a react app.
As it's not possible to navigate to the auth0 login page, I've been using a command that generates the user token using the API
POST /oauth/token
, and then inject the token in the cookies and redirect the front end as it would occur after a successful login using the state param.But since version 1.12, the behavior has changed and the auth0 sdk does not seem to accept this way to inject authentication.
Here is the code I'm using to authenticate the browser automation (This code still works using 1.11):
Maybe it's a bad thing to do, but it seemed fine to me to do it that way, if you have any other option available I would be glad to hear about how I should do it differently.
What was the expected behavior?
I was expecting that the state handling would be the same after the version upgrade.
Reproduction
a0.spajs.txs.${stateId}
as the login page would do/?code=any-code&state=${stateId}
Can the behavior be reproduced using the SPA SDK Playground?
Environment
auth0-spa-js
used: 1.12.1The text was updated successfully, but these errors were encountered: