I want to athenticate myself (React application) using cypress.js (https://www.cypress.io/). Is there a way to do it programatically with PKCE? As i was reading and looking into all examples - all of them are using implicit flow
I was trying to use solutions like https://www.npmjs.com/package/react-adal
but with no success as it needs an implicit flow to be turned on
I was trying this as well: https://xebia.com/blog/how-to-use-azure-ad-single-sign-on-with-cypress/ with no success
I expected to programatically signin inside cypress and save user info and access_token to sessionStorage to be able to perform another api calls
I have not found a way to do this programmatically with PKCE per se, but with the MSAL 2.0 library (@azure/msal-browser
on npm) I was able to fill in the account cache ahead of time so it thought it was already logged in. The process looks like this:
cy.task
, send a request to Azure AD using the ROPC flow to get the tokens.
const scopes = [
'openid',
'profile',
'user.read',
'email',
'offline_access' // needed to get a refresh token
];
const formdata = new URLSearchParams({
'grant_type': 'password',
'scope': scopes.join(' '),
'client_info': 1, // returns an extra token that MSAL needs
'client_id': aadClientId,
'client_secret': aadClientSecret,
'username': aadUsername,
'password': aadPassword,
});
const response = await fetch(`https://login.microsoft.com/${aadTenantId}/oauth2/v2.0/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formdata.toString(),
});
const tokens = await response.json();
// The token tells us how many seconds until expiration;
// MSAL wants to know the timestamp of expiration.
const cachedAt = Math.round(new Date().getTime()/1000);
const expiresOn = cachedAt + tokens.expires_in;
const extendedExpiresOn = cachedAt + tokens.ext_expires_in;
// We can pull the rest of the data we need off of the ID token body
const id_token = JSON.parse(Buffer.from(tokens.id_token.split('.')[1], 'base64').toString('utf-8'));
const clientId = id_token.aud;
const tenantId = id_token.tid;
const userId = id_token.oid;
const name = id_token.name;
const username = id_token.preferred_username;
const environment = 'login.windows.net'; // 🤷♂️
const homeAccountId = `${userId}.${tenantId}`;
const cacheEntries = {};
// client info
cacheEntries[`${homeAccountId}-${environment}-${tenantId}`] = JSON.stringify({
authorityType: 'MSSTS',
clientInfo: tokens.client_info,
environment,
homeAccountId,
localAccountId: userId,
name,
realm: tenantId,
username,
});
// access token
cacheEntries[`${homeAccountId}-${environment}-accesstoken-${clientId}-${tenantId}-${token.scope}`] = JSON.stringify({
cachedAt: cachedAt.toString(),
clientId,
credentialType: "AccessToken",
environment,
expiresOn: expiresOn.toString(),
extendedExpiresOn: extendedExpiresOn.toString(),
homeAccountId,
realm: tenantId,
secret: tokens.access_token,
target: tokens.scope,
});
// id token
cacheEntries[`${homeAccountId}-${environment}-idtoken-${clientId}-${tenantId}-`] = JSON.stringify({
clientId,
credentialType: "IdToken",
environment,
homeAccountId,
realm: tenantId,
secret: tokens.id_token,
});
// refresh token
cacheEntries[`${homeAccountId}-${environment}-refreshtoken-${clientId}--`] = JSON.stringify({
clientId,
credentialType: "RefreshToken",
environment,
homeAccountId,
secret: tokens.refresh_token,
});
cy.window
to store those in sessionStorage or localStorage, depending on how you have MSAL configured.
cy.task('login').then(cacheEntries => {
cy.window().then(window => {
for (let entry in cacheEntries) {
window.sessionStorage.setItem(entry, cacheEntries[entry]);
}
});
});
It's super fragile and not very pretty, but it works! The user that Cypress logs in as needs MFA to be disabled, of course.