Azure AD Authentication through Cypress Automation Testing

Juvith Ghosh
KPMG UK Engineering
5 min readDec 15, 2023

Frontend testing is one of the crucial step before we do the final release/s of our increment/s which could make our stakeholders feel the satisfaction of quality work that they have been expecting. As most of the teams nowadays follows the Scrum principles, it becomes mandatory to make the release/s sooner for getting early ROI (Return on Investment) from the business perspective. In the long run, developers tend to bring new features but sometimes introduces new bugs which might break the already released feature and thus adversely affecting the quality of the software being released.

In today’s market, most of the software products are using secure login features either through some of their federated services such as Microsoft, Facebook or creating their own login. This assists in maintaining the security best practices and reduces the risks of getting your platform malice by hackers. In this tight pool of time, when there are Epics upon Epics for developers to keep an eye on, it becomes a necessity to bring automation testing into practice. But, have you ever imagined how it would be when your web app is using Azure AD authentication technique and you need to pass that for successfully doing an End-to-End testing of your user story? No worries, I would be defining you here the easiest steps which you could follow to pass such tight Microsoft AuthN (Authentication) when you are using Cypress as your automation testing tool.

Note: We will be using Typescript setup but you can complete similar setup when using Javascript. When using Javascript and you need to convert to Typescript, you can easily do it by setting up the tsconfig.json file and renaming the original file version to .ts/.tsx.

Installing Cypress

If you’re new to cypress then you first need to install cypress using terminal command but make sure you already have updated node module packages. Change the root directory to your project path by:

cd/project_path
npm install cypress --save-dev

Remember to check your proxy settings, VPN and then open the project package.json file to find out of that the correct version have been embedded onto it. You can also directly download the Cypress desktop app from the official portal. Once you install it, try setting up your first end-to-end testing schemas using the on-screen instructions. This would allow you to run your tests smoothly on your locals and you can get a grip of the Mocha and Chai assertion framework language.

Storing User Login Credentials

On your current project path, check the files which got newly created by Cypress. Try locating a file in your ClientApp naming “cypress.config.ts”. This is where you need to do the following changes and update the user credentials. For security reasons, the values of the keys for name, username and password have been removed.

import { defineConfig } from "cypress";

export default defineConfig({
e2e: {
baseUrl: null,
chromeWebSecurity: false,
supportFile: "cypress/support/e2e.ts",
env: {
tenantId: "",
clientId: "",
clientSecret: "",
name: "",
username: "",
password: "",
emAppSiteUrl: "",
loginUrl: "../public/login.html",
experimentalSessionSupport: false,
experimentalSessionAndOrigin: true,
},
testIsolation: false,
experimentalOriginDependencies: true,
experimentalModifyObstructiveThirdPartyCode: true,
specPattern: "**/*spec.ts",
video: false,
},
component: {
specPattern: "**/*spec.tsx",
supportFile: false,
video: false,
devServer: {
framework: "create-react-app",
bundler: "webpack",
},
},
});

Creating Chaining Function

Navigate to the Cypress “Support” folder and then create an “index.d.ts” file to edit the chaining function which receives the user credentials during login and pass the value to the cypress commands after picking it up from the config file.

//eslint-disable-next-line @typescript-eslint/no-namespace, @typescript-eslint/no-unused-vars
declare namespace Cypress {
interface Chainable {
loginToAAD(username: string, password: string): Chainable<void>;
}
}

Azure AD Login

Before you login using cypress, make sure you’re having adequate knowledge on cookies and cypress commands such as cy.origin() and cy.session(). Details of these commands are available on the official portal.

Now as the next step, we need to overwrite the “command.ts” file inside the support folder of cypress.

// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
function loginViaAAD(username: string, password: string) {
// Login to your AAD tenant.
cy.origin(
"login.microsoftonline.com",
{
args: {
username,
},
},
({ username }) => {
cy.get('input[type="email"]').type(username, {
log: false,
});
cy.get('input[type="submit"]').click();
}
);

// depending on the user and how they are registered with Microsoft, the origin may go to live.com
cy.origin(
"login.microsoftonline.com",
{
args: {
password,
},
},
({ password }) => {
cy.get('input[type="password"]').type(password, {
log: false,
});
cy.get('#idSIButton9.win-button.button_primary.button.ext-button.primary.ext-primary').click();
}
);
}

Cypress.Commands.add("loginToAAD", (username: string, password: string) => {
cy.session(
`aad-${username}`,
() => {
const log = Cypress.log({
displayName: "Azure Active Directory Login",
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
});

log.snapshot("before");
cy.visit(Cypress.env("loginUrl"));
cy.wait(1000);
cy.get("a#SignIn").click();
loginViaAAD(username, password);

log.snapshot("after");
log.end();
},
{
validate: () => {
cy.wait(6000);
// this is a very basic form of session validation for this demo.
// depending on your needs, something more verbose might be needed
cy.visit("https://localhost:5001");
cy.wait(6000);
},
}
);
});

Note: I’m running my web app on localhost: 5001 but you can select any port depending your project setup. Also, maintain an adequate wait time before cypress does its login to AAD.

Caching for Re-login

You may need to re-login after you do your first login using the cy.origin(). Now, cy.session() stores the cookies and assists you quicker login for you to test the other user stories within the same session. You need to add the following code before you write another test.

 describe("Azure Active Directory Authentication", () => {
before every test case login is mandate...
before(() => {
cy.loginToAAD(Cypress.env("username"), Cypress.env("password")); // login function
});

//before every test case it will check for cookies exist or not....
beforeEach(() => {
cy.wait(3000);
cy.getCookie(".AspNetCore.Cookies").should("exist");
});

//after login sucessfull...
it("LogIn Successfull", () => {
cy.wait(3000);
cy.get("h1").should("contain", `Engagements`); // uses cookie preserved by cy.session cache
cy.get("span[class='MuiButton-label']").should(
"contain",
Cypress.env("name")
);
cy.get(
"div.makeStyles-buttonContainer-64 > button.MuiButtonBase-root > span.MuiButton-label"
)
.should("contain", "New Engagement")
.click();
});

//2nd test case.
it("Create New Training Engagement", () => {
cy.url()
.should("contain", "/engagement/create");

Note: I’m using .NET as backend and therefore need to store the cookie values using “cy.getCookie()” for .AspNetCore but you need to figure it out if there is other way to sort your issue for storing the cookie when using some other language for your backend.

Conclusion

Cypress automation testing tool can make the life of developers very easy as it can quickly test multiple stories in short pool of time and can assist developers for faster release the product features in the market. It also takes care of the federated login such as Microsoft AAD securely and provides the full end-to-end quality testing.

--

--