Cypress Login with CSRF token - javascript

I would really like to expand on this topic "Logging in with CSRF token" as I have been banging my head against a wall for weeks now and I can't be the only one with this problem. All topics about logging in via POST or logging in with CSRF inevitably lead back to the above link.
Yet the recipes described in this link do not seem to work for me. They all assume that the CSRF token is created once you visit the Login page. But on our site, the CSRF token is only created once you login.
I tested with Postman and there is no CSRF token in the HTML or in the header before you are logged in.
I also tested it in Cypress with the following code:
describe('gimme dat csrf token', () => {
it('try to get the csrf token', () => {
cy.visit(Cypress.env('url'))
cy.getCookie('YII_CSRF_TOKEN')
.then(async (c) => {
cy.log(c.value)
return c.value
})
})
})
This will return an error as there is no YII_CSRF_TOKEN
Type Error
Cannot read properties of null (reading 'value')
If I add a login step before, it will return the value of the CSRF token as expected:
import {Login} from "../../pages/login/Login";
describe('gimme dat csrf token', () => {
it('try to get csrf token', () => {
cy.visit(Cypress.env('url'))
login.loginCredentials(Cypress.env('userEmail'), Cypress.env('userPass')) //added login
cy.getCookie('YII_CSRF_TOKEN')
.then(async (c) => {
cy.log(c.value)
return c.value
})
})
})
Therefore strategies #1 (parse token from HTML) and #2 (parse token from response headers) from the above link can not work.
Recipe #3 is also not feasible as we have several live systems to test and we can't expose a /csrf route
This only leaves us with the strategy #4, which we have been using so far.
Any ideas or are we stuck with adding the "manual" login step to every single spec file?

I think strategies #1 & #2 rely on the browser remembering credentials and supplying them to the login page, as happens with the Stackoverflow page - you don't have to log in every time you visit.
The main difference is you have used cy.visit() instead of cy.request() as shown in the recipes.
If you still are not able to successfully grab the token, try using your login with cy.session(). It will only call the login function once per session.
/*
Enable use of cy.session() and new behavior to handle caching
and restoring cookies, localStorage, and sessionStorage.
*/
Cypress.config('experimentalSessionSupport', true)
describe('...', () => {
beforeEach(() => {
cy.session(() => {
login.loginCredentials(Cypress.env('userEmail'), Cypress.env('userPass'))
})
})
it('try to get csrf token', () => {
cy.getCookie('YII_CSRF_TOKEN')
.then((c) => {
cy.log(c.value)
})
})
})

Related

How to retrieve Token for API calls in cypress

Due to a lack of knowledge in JS, I do face with below problem.
I do automation testing with cypress and for external API endpoints, I need to use Dynamic Token that expires every 3600 secs.
I did some short research and find out that, for reaching to JWT Token I will need 1st SSO login (that my app has). Then use the below script.
But I don't know what to do after this.
it('the value of JWT Token should exist in localStorage', () => {
cy.getLocalStorage('JWT_DATA').then(lsData => {
cy.log('the token', lsData); // I get JWT Token in here
});
});
The result of this script is only printing the TOKEN.
But I need to store it somehow and then be able to reuse it for every different API endpoint call.
You can use Cypress.env for this. Then you can use the token throughout the test anywhere.
it('the value of JWT Token should exist in localStorage', () => {
cy.getLocalStorage('JWT_DATA').then((lsData) => {
cy.log('the token', lsData) // I get JWT Token in here
Cypress.env('token', lsData)
})
})
To use it, you can do
Cypress.env('token')

Handling Firebase auth authentication

I need some help understanding if I am handling the authentication/authorization correctly with Firebase Auth, JS and Python.
Once the user has signed in, I capture the idToken and create a cookie in the browser:
firebase.auth().signInWithEmailAndPassword(email, pass)
.then(({user}) => {
return user.getIdToken().then((idToken) => {
if (idToken) {
document.cookie = "token=" + idToken;
window.location.assign('/profile');
} else {
document.cookie = "token="
}
})
})
.catch((error) => {
//handle error here
});
The route /profile should be protected so I created a decorator and I retrieve the cookie and verify it:
id_token = request.cookies.get("token")
if not id_token:
return redirect(url_for('login'))
try:
decoded_token = auth.verify_id_token(id_token)
uid = decoded_token['uid']
except Exception as e:
print("Exception: {}".format(e))
return redirect(url_for('login'))
This is working so far but I want to see if this is the ideal situation from a security perspective. Also, what about the onAuthStateChanged? How should I handle it in the case above?
Firebase SDKs send the ID token with each request in the Authorization header, so sending it in a cookie is not going to more or less dangerous than that.
Instead of determining the token in signInWithEmailAndPassword though, I'd instead monitor ID token generation by listening to onIdTokenChanged events and using that moment to update your cookie.

Auto-login in nuxtjs using only token

I have a mobile app that uses laravel passport as a mean of authentication (no problem here). In the mobile app, I added a button that will theoretically redirect me to my web app (same auth endpoint as the mobile) and automatically logs me in. How can I achieve this using nuxtjs (with nuxt auth already used)? Can't find a way to login using only the token in nuxt auth or hack it.
Please feel free to add if you have any suggestions or recommendations.
I imagine something like this but no luck.
this.$auth.loginWith('local', {
data: {
token: token <--------------------------- token from url (originated in mobile app)
}
})
.then(response => {
// successful login
})
.catch(error => {
// failed login
});
This is my sample url that I want to automatically login in nuxtjs
http://example.com/redirect?token=tokenvaluehere
I had a similar problem as you, but instead of laravel passport I used jwt-auth with local Strategy.
I achieve the auto-login functionality using the code below. Maybe it helps:
const route = ''; // Route from where to get the Token
this.$auth.reset().then(() => {
this.$axios.get(route)
.then(({data}) => {
this.$auth.setStrategy('local').then(() => {
const token = this.$auth.strategy.options.tokenType
? this.$auth.strategy.options.tokenType + ' ' + data.login_token
: data.login_token
this.$auth.setToken('local', token)
this.$auth.strategy._setToken(token)
this.$auth.fetchUser()
})
})
})

How to get JWT cookies in our react application, how to check the user is login or not I am unable to find how to handle my react application session

How to get JWT cookies in our react application, how to check the user is login or not I am unable to find how to handle my react application session.
I really appreciate who helps me out with this problem.
Thanks in advance
The server side API is setting the HTTPOnly cookie which you wont be able to read in JS.
What you need to do it in your react App handle a 401 status error and based on that set a flag isAuthenticated or something as false. Otherwise keep it to be true. With each request to the server HTTPOnly cookie would be sent automatically so you don't need to handle the token inside a cookie.
The backend code needs to send a 401 once the cookie is expired, or the logout is requested or the JWT inside a cookie expires.
Before I say anything, you have included app.use(cookieParser()) in index.js right? Because if not, you're gonna need that once you've installed it with npm i cookie-parser
But anyway, a few things:
You can create a PrivateRoute in React, as far as I'm aware this tends to work well to protect routes from unauthorized users.
You can simply store an isAuthenticated in either the state or localStorage: however this will require that you make absolutely sure that a user shouldn't be able to just change the value in the state or add isAuthenticated in localStorage and just spoof authenticity (this is the part that took me the longest to get right).
Anyway, the way I've handled this is that when a user logs in an access token (and a refresh token if it doesn't already exists) are generated from the server and sent to the client in an httpOnly cookie, while this makes it so that you can't do anything with it on the client side with JavaScript as Pavan pointed out in his answer (which is generally a good thing), you should use res.status for validation when you make the fetch request. Here's what my fetch request kind of looks like:
const login = async (user) => {
const body = JSON.stringify(user);
return fetch(loginURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'include', //this is important
body: body
}).then(function(res) {
if (res.status === 200) {
const id = Date.now();
localStorage.sid = id; //this is temporary
return res.json()
} else {
return res.json()
}
})
// you can ignore this part
.then(async resp => {
return resp
})
}
Side-note: Your browser automatically handles the httpOnly cookies you send from your server however the credentials: 'include' needs to be included in your subsequent fetch requests.
In one of my main parent components, the login function is called:
login = async (user) => {
this.setState({ error: null });
await adapter.login(user).then(data => {
if (!data.error) {
this.setState({session: "active"})
} else if (data.error && data.error.status === 401) {
// this is so I can handle displaying invalid credentials errors and stuff
this.setState({ error: data.error, loading: false });
}
});
}
I also have a middleware on the server-side that is run before any of the code in the routes to verify that the user making the request is actually authorized. This is also what handles access token expiration for me; if the access token has expired, I use the refresh token to generate a new access token and send it back in the httpOnly cookie, again with a status of 200 so the user experience isn't jarring. This does of course mean that your refresh token would have to live longer than your access token (I haven't decided how long in my case yet, but I'm thinking either 7 or 14 days) though as far as I'm aware that is okay to do.
One last thing, the parent component I referred to earlier calls a validate function which is a fetch request to the server within its componentDidMount so that the user is verified each time the component mounts, and then I've included some conditional rendering:
render() {
return (
<div className="container">
{
!localStorage.sid && <LoginForms {...yourPropsHere}/>
}
{
this.state.loading && <Loading />
}
{
localStorage.sid && this.state.session === "active" && <Route path="/" render={(props) => <Root error={this.state.error} {...props}/>}/>
}
</div>
);
}
I've gone the conditional rendering route as I couldn't get PrivateRoute to work properly in the time that I had, but either should be fine.
Hopefully this helps.
Your login API should return JWT token and how long it should be live.
Your login API response would be like :
{
jwt: your jwt token,
duration: in seconds
}
Use universal-cookies NPM to store this result into cookies.
For more details how to manipulate with cookies, visit
https://www.npmjs.com/package/universal-cookie
For setting cookies your code like:
const cookies = new Cookies();
cookies.set(name of cookies, jwt value from API call, {
maxAge: duration,
});
Above code store the jwt cookies in browser and after maxAge automatically remove it from browser.
So for identifying session is present or not, you should check after specific interval cookies has present in browser or not. If cookies has present in browser then session is on, otherwise session has expired.

Keeping user logged in after refresh/using refresh token with Google OAuth2 in React app

I’m building a React app where a key part of the functionality is a user can sign into their Google account and then access a feed of their most recent Google Drive/Docs mentions and notifications. A user arrives at my site where I load the Google OAuth2 client with my client_id, apiKey, scope and discoveryDocs, and they can click a button to sign in. For convenience, I’d like the user to not have to re-login and re-auth with their Google account every time they use the app or the app refreshes, I’d like the login information to be saved across sessions. For this I’ll use localStorage to start but eventually integrate a database like Firebase.
After looking through the JavaScript client Google OAuth2 docs I understand how most things work - understand the data and methods stored in the GoogleUser, GoogleAuth, etc objects. I’m having a little trouble with access and refresh tokens. I recognize that you can get the authenticated user’s information through gapi.auth2.getAuthInstance().currentUser.get() and gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse() returns an object with a lot of what I think I need like id_token, access_token and metadata like expires_at and token_type. I also see the grantOfflineAccess() method from which I extract response.code, but I’m not quite sure which of these tokenized strings is the right one to use and how I need to use it.
This FAQ from Google (https://developers.google.com/api-client-library/javascript/help/faq) is somewhat helpful but advises to Refresh the token by calling gapi.auth.authorize with the client ID, the scope and immediate:true as parameters., but gapi.auth.authorize is noted by Google in the client JS OAuth2 library as being incompatible with the more widely used and heavily documented api.auth2.init and signIn.
I also have a vague idea from posts like Google OAuth2 API Refresh Tokens that I need to follow server-side OAuth2 instructions and I can only get this refresh_token through a server-side call, but I’m still at a bit of a loss. I’ll caveat and say I’m more of a front end developer/designer so I'm shaky on my node and server-side skills.
TL;dr: I don't know how to keep my users who signed in via Google OAuth2 signed in after a refresh. I have an idea it's due to refresh_token and access_token and I have access to them but I don't know what to do after that, in terms of sending data to Google servers, getting information back, and setting the token information for the given user when they return.
Here's my method that calls on componentDidMount (basically when my app first loads):
loadGoogleClient = () => {
gapi.load("client:auth2", () => {
gapi.auth2.init({
'client_id': my-client-id,
'apiKey': my-key,
'scope': "https://www.googleapis.com/auth/drive.readonly",
'discoveryDocs': ['https://content.googleapis.com/discovery/v1/apis/drive/v3/rest']
})
// Listen for sign-in state changes.
console.log(`User is signed in: ${gapi.auth2.getAuthInstance().isSignedIn.get()}`);
gapi.client.load("https://content.googleapis.com/discovery/v1/apis/drive/v3/rest")
.then(() => { console.log("GAPI client loaded for API");
}, (error) => { console.error("Error loading GAPI client for API", error);
});
console.log('Init should have worked');
});
}
And here's my code that's onClick on my Signin button:
authGoogle = () => {
gapi.auth2.getAuthInstance()
.signIn({scope: "https://www.googleapis.com/auth/drive.readonly"})
.then(function() { console.log("Sign-in successful"); },
function(err) { console.error("Error signing in", err); });
}
If you are using the client lib (the gapi api) there is no need for a refresh token... Once logged in it should persist across sessions and refreshes... The issue is the code...
1) Include this in your index.html in the head section:
<script src="https://apis.google.com/js/api.js"></script>
2) Here is a component that will handle auth using the gapi lib and render a button conditionally (The code is self-explanatory but if you have a question just ask...)
import React from 'react';
class GoogleAuth extends React.Component {
state = { isSignedIn: null };
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: '<your client id here...>',
scope: 'email', // and whatever else passed as a string...
})
.then(() => {
this.auth = window.gapi.auth2.getAuthInstance();
this.handleAuthChange();
this.auth.isSignedIn.listen(this.handleAuthChange);
});
});
}
handleAuthChange = () => {
this.setState({ isSignedIn: this.auth.isSignedIn.get() });
};
handleSignIn = () => {
this.auth.signIn();
};
handleSignOut = () => {
this.auth.signOut();
};
renderAuthButton() {
if (this.state.isSignedIn === null) {
return null;
} else if (this.state.isSignedIn) {
return <button onClick={this.handleSignOut}>Sign Out</button>;
} else {
return <button onClick={this.handleSignIn}>Sign in with Google</button>;
}
}
render() {
return <div>{this.renderAuthButton()}</div>;
}
}
export default GoogleAuth;
Now you can simply use this component/button anywhere in your app... Meaning if you have a Navigation component simply import it there and use it as a button login / log out...

Categories

Resources