cy.session() with cy.request() does not log in user - authentication

I set up (in my cypress commands):
Cypress.Commands.add("loginByApi1", (username1, password1, TrackingId1) => {
cy.session([username1, password1, TrackingId1], () => {
cy.request({
method: 'POST',
url: 'https://someWebPage/api/challenge/credentials',
body: {
email: username1,
password: password1,
TrackingId: TrackingId1
}
}).then(({body}) => {
window.localStorage.setItem('authToken', body.token)
cy.log(body.token)
})
})
This is the endpoint with parameters used for login.
I call in my test
cy.loginByApi1(Cypress.env('mainInvestorUserEmail'), Cypress.env('mainInvestorUserPassword'), 'Cypress.env('trackingID')')
This is the session body (passes successfully it seems - see image) Notice that cy.log(body.token) is empty
When I try cy.visit('/') in order to visit my base page, URL is still redirected to login screen
In my local storage i have these keys
I tried replacing authToken with accessToken, since this is shown in local storage, but same result.
Not sure if I'm doing it correctly here, but this is on the official cypress site and other sites as well.
I can login using UI so my credentials and approach in that regard are correct.

Related

PassportJS OAuth2Strategy: authenticate returns 400 instead of redirecting

I'm trying to setup discord oauth2 pkce using passportjs and the passport-oauth2
const discordStrategy = new OAuth2Strategy({
authorizationURL: 'https://discord.com/api/oauth2/authorize',
tokenURL: 'https://discord.com/api/oauth2/token',
clientID: DISCORD_CLIENT_ID,
clientSecret: DISCORD_CLIENT_SECRET,
callbackURL: DISCORD_CALLBACK_URL,
state: true,
pkce: true,
scope: ['identity', 'scope'],
passReqToCallback: true,
},
(req: Request, accessToken: string, refreshToken: string, profile: DiscordUserProfile, cb: any) => {
prisma.user.findUnique({ where: { email: profile.email ?? '' }}).then(foundUser => {
if (foundUser === null) {
// Create a new user with oauth identity.
} else {
cb(null, foundUser)
}
}).catch(error => {
cb(error, null);
})
});
I've been following the google example as well as some others, these examples indicate that, I should be able to use:
passport.use('discord', discordStrategy);
and
authRouter.get('/discord', passport.authenticate('discord'));
and this should redirect to the OAuth2 providers login page, but instead, I get a 400 Bad Request "The request cannot be fulfilled due to bad syntax." The response body contains an object:
{"scope": ["0"]}
Why is this happening instead of the expected redirect?
My intention is that, once the user logs in, I should get a code, then I can post that code and the code verifier to get an access token, then once the access token is obtained, the actual authenticate call can be made
Edit: I put breakpoints in the passport.authenticate function and I stepped through it. It does actually get through everything and it calls the redirect. The parsed URL it generates, even if I copy it and manually navigate to the URL, it gives me the same, just gives:
{"scope": ["0"]}
and no login page, why?
If you add a version number to the base api url, e.g. /v9 it gives a full error message.
It turned out I had typo'd the scopes, I had 'identity' instead of 'identify' - now this part of the process is working as expected.

Auth0 refresh token in React Native fails with 401

In my React Native app -- init app not Expo -- I'm trying to refresh the access_token but my POST call is failing with 401. I'm testing this functionality so I make the POST call some 30 seconds after I login so not sure if this plays a role or not.
In my initial login, I do get a refresh_token along with a valid access_token. I then tell my app to wait 30 seconds and make a POST call that looks like this:
const url = 'https://mydomain.auth0.com/oauth/token';
const postOptions = {
method: 'POST',
url: url,
headers: {
"content-type": 'application/x-www-form-urlencoded'
},
form: {
grant_type: 'refresh_token',
client_id: 'MY_CLIENT_ID',
refresh_token: 'REFRESH_TOKEN_RECEIVED_DURING_LOG_IN'
}
};
fetch(url, postOptions)
.then((response) => {
debugger;
// this is where I get response.status 401
})
Any idea what the issue is here?
Also want to mention that under my application settings, Refresh Token is checked under "Grant Types" but refresh token rotation or expiration are NOT enabled.
I figured this out and sharing it in case others need it in the future.
First, Auth0 documentation is misleading at best. They keep mentioning a regular POST call which doesn't work.
In my React Native app, I use their react-native-auth0 library. This library does offer a refreshToken() method which is what I ended up using.
Before I share the code, here are a couple of really important points:
Be sure to include offline_access in the scope of your initial authentication call for the user. Without including offline_access in your scope, you won't get a refresh_token. Once you receive it along with your access_token and id_token, store it as you'll use it many times. This brings me to the second point.
Unless you set it otherwise, your refresh_token doesn't expire. Therefore, store it some place secure and NOT just in AsyncStorage. As mentioned above, unless, you set it otherwise or it gets revoked, your refresh_token doesn't expire and you use it again and again.
With that said, here's the code. Please keep in mind that at start up, I initialize auth0 as a global variable so that I can access it in different parts of my app.
Here's what my initialization looks like in index.js:
import Auth0 from 'react-native-auth0';
global.auth0 = new Auth0({
domain: "MY_DOMAIN.auth0.com",
clientId: "MY_CLIENT_ID",
});
And here's how I use the refreshToken() method:
// First, retrieve the refresh_token you stored somewhere secure after your initial authentication call for the user
global.auth0.auth.refreshToken({ refreshToken: 'MY_REFRESH_TOKEN' })
.then(result => {
// If you're doing it right, the result will include a new access_token
})
you probably need to add the authorization header with your access_token:
const url = 'https://mydomain.auth0.com/oauth/token';
const postOptions = {
method: 'POST',
url: url,
headers: {
"content-type": 'application/x-www-form-urlencoded',
"Authorization" 'bearer '+access_token,
},
body: JSON.stringify({
grant_type: 'refresh_token',
client_id: 'MY_CLIENT_ID',
refresh_token: 'REFRESH_TOKEN_RECEIVED_DURING_LOG_IN'
});
};
fetch(url, postOptions)
.then((response) => {
debugger;
// this is where I get response.status 401
})

How do you test user flows that involve confirmation by email?

I mean functional or E2E testing. That's all clear with generic flows, but when it comes to transactional emails (signup confirmations, password resets, purchase notifications and others) it's still bringing questions. After some research I came up with a few ideas. One is to leverage Restmail.net API (here examples with Selenium WebDriver and Cypress - http://dsheiko.com/weblog/testing-sign-up-flow-with-activation-by-email). It's free, but API is public. So it's not really suitable for email messages with potentially sensitive information. Another approach to access Gmail inbox via IMAP bridge or Gmail API (here the explanation and code snippets - https://docs.puppetry.app/testing-emails/example-with-imap-bridge). But again, it's rather a workaround.
I know there are guys like Sendgrid, Mailgun, Email Yak, Postmark. I don't want to pay that much. So how do you folks do it? It it a thing to you?
We're doing this using Mailosaur email addresses for our test users. We then use a cypress custom command to query Mailosaur for the expected email. It was super easy to set up.
Here's the main part of that custom command, which is all we had to add to start doing email testing. You can refer to their API docs for what query, mailosaurServer, and MailosaurApiKey should be.
Cypress.Commands.add("getEmailFromMailService", query => {
return cy
.request({
method: "POST",
url: `https://mailosaur.com/api/messages/await?server=${mailosaurServer}`,
body: query,
headers: { "Content-Type": "application/json" },
auth: { user: mailosaurApiKey },
})
.then(response => {
expect(response.status).to.equal(200);
return response.body;
});
});
You could create a post request for the "forgot your password" and then assert on it.
something like:
cy.visit('yoursite')
cy.get('#forgotpassword').click().then(function (xhr) {
cy.server()
cy.request('POST', 'APIforForgotPassword').as('sucessfullemail)
})
cy.get(#sucessfullemail).then(function (xhr) {
expect(xhr.status).to.eq(200)
Cypress.Commands.add('ConfirmUser', () => {
const confirmationToken = null;
cy.request({
url: 'http://localhost:3000/api/confirmation_token?email=test_user#cypress.com',
followRedirect: false
})
.then((resp) => {
confirmationToken = resp.token
})
cy.visit('/en/confirmation?confirmation_token=token')
})
Create the API that requires the email as a parameter and returns the confirmation-token. call the API from cypress commands as ajax-request and get the response token

How to send Oauth token from callback at the server to the client?

I am using expressJs and passport for authentication. I am using Google Oauth2.0 for login with standard Passport GoogleStrategy. At client I am using axios for sending a login request to the server. My login routes are :
router.get(
"/google",
passport.authenticate("google", { scope: ["profile", "email"] }));
router.get(
"/google/callback",
passport.authenticate("google", { failureRedirect: "/"}),
function(req, res) {
const token = jwt.sign({id: req.user.id}, configAuth.secretKey);
console.log("generated token: ", token);
res.json({success: true, token: 'bearer ' + token});
}
);
I am using the user information from the callback to generate the JWT which I want to sent the client.
At the client I am using axios to send request and get the JWT and store it in localstore.
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})
.catch((error) => {
// if(error.response.status === 401) {
// this.setState({ message: 'Login failed. Username or password not match' });
// }
console.log("Login error", error);
});
But Axios doesn't wait for the redirect to happen and returns a HTML document with Loading... message. If you try to access the API in the browser, it returns the desired JSON object. Is there a way to wait for redirects. Should I use another library to send login request?
I tried sending the token as url parameter with
res.redirect()
but client and server are at different ports so it doesn't work.
Is there another way to do it?
Google's OAuth2 pathway redirects your browser, resulting in page reloads, a couple of times before it completes. As a result, your client-side code,
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})...
will never reach the .then() block. You probably see this in the browser; you click a button or something to navigate to 'http://localhost:3001/google', and your localhost:3001 server re-directs your browser to a Google login page. Now that your browser is at the login page, it has no memory of the axios.get statement above--that webpage code is gone.
You need to handle the JWT in client-side code that your server sends in response to
axios.get('http://localhost:3001/google/callback').
This is your browser's final stop in the OAuth2 path--once you get there, you won't be re-directed again. You can put your axios.get function above inside that client-side code.
If you haven't solved the problem, there is a workaround use 'googleTokenStategy' instead of googleOAuth on passportjs. That way you can use react's GoogleLogin plugin to receive the access token from the front end and send it by axios.post to the backend link then set up the jwt. Reference here

Programmatically authenticate in Auth0

I'm setting up cypress.io to test a react app. I'm trying to programmatically authenticate with Auth0 Lock so I don't have to test their UI. Here are the steps:
POST to https://[auth0-domain].com/usernamepassword/login
client_id: "",
client_secret: "",
audience: "audience",
redirectUri: "http://[auth0-domain].com:8383/callback",
scope: "openid email crud:all",
protocol: "oauth2",
sso: true,
username: "",
password: "",
connection: "conn",
tenant: "tenant",
popup: false,
Collect returned form data
POST to https://[auth0-domain].com/login/callback
wa=wsignin1.0&wresult=token&wctx=metastuff
The first steps works but in the third step I'm able to post to login/callback but I get an HTML page with this error message:
Looks like something went wrong! There could be a misconfiguration in the system or a service outage. We track these errors automatically, but if the problem persists feel free to contact us with this tracking id: <b>e88e08e43b7cdee78458</b>.<br/>Please try again.
I'm wondering if there is something with Auth0 blocking me from doing this or if I'm not sending the correct data/header.
I ended up using the auth0-js module which calls the auth0 callback. Then I use 'should' to wait for the localStorage to be set:
import auth0 from 'auth0-js';
import url from 'url';
Cypress.Commands.add('login', () => {
const auth = new auth0.WebAuth({
audience: 'http://audience',
domain: 'domain.auth0.com',
clientID: 'qqqqqqqqqqqq',
redirectUri: 'http://localhost/callback',
responseType: 'token id_token',
scope: 'openid email profile name username groups roles',
sso: true
});
auth.login({username: 'username', password: 'pass'});
cy.window().its('localStorage.user_profile').should('exist')
});