Azure Active Directory Authentication
What you'll learn
- How to set up Cypress to test against an Azure Active Directory web app
- How to authenticate with Azure Active Directory using
cy.origin()
This guide is designed for testing against a Single Page Application (SPA) that
uses
Azure Active Directory
(AAD) to authenticate users. For this guide, the Microsoft Authentication
Library
@azure/msal-browser
package is used by the web app to broker this authentication.
This guide can also serve as a foundation for testing other web apps with Cypress that use Azure Active Directory services with other frameworks, such as React, Angular, or Vue.
Microsoft AAD Application Setup
For this guide, we are mainly going to focus on setting up Cypress to test against an Azure Active Directory web app. Please clone the Microsoft Identity JavaScript Tutorial and follow the steps to set up your application.
Once set up, you'll need to modify a few things in the App/index.html
file:
- Remove any
integrity
attributes inside<script>
tags. This is to prevent any SRI attribute mismatches, which can happen since Cypress must rewrite any framebusting code. This is recommended only when an application is under test. - Uncomment the
authRedirect.js
<script>
tag and comment out theauthPopup.js
<script>
tag. Authentication Pop ups will not work inside of Cypress.
Inside server.js
, you'll see a reference to express-rate-limit
, which
limits each window to 100 requests per 15 minutes. Under test, your application
will likely exceed this as you will be reloading your application and making
requests to broker/verify authentication. For this demo, you can either
- Remove the rate limiting code (recommended for demo purposes only).
- Implement a strategy to increase the rate limit under test.
When finished, your SPA should be running on http://localhost:3000
and be
properly configured to run with Azure Active Directory and Cypress.
Configuring Cypress to use Microsoft AAD
To have access to test user credentials within our tests, we need to configure
Cypress to use the
Azure Active Directory
user credentials for a given user in your tenant. These user credentials should
be set in the cypress.env.json
file inside the root directory of your project.
{
"aad_username": "AAD_USERNAME",
"aad_password": "AAD_PASSWORD",
"aad_name": "AAD_NAME"
}
Also, to authenticate with Azure Active Directory, you'll need to enable the
experimentalModifyObstructiveThirdPartyCode
configuration option in the e2e configuration. If not enabled, your
authentication workflow will enter an infinite redirect loop.
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
experimentalModifyObstructiveThirdPartyCode: true,
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
experimentalModifyObstructiveThirdPartyCode: true,
},
})
Login with cy.origin()
Next, we'll write a custom command called loginToAAD
to perform a login to
Azure Active Directory.
This command will use cy.origin()
to
- Navigate to the Azure Active Directory login page on
login.microsoftonline.com
- Input user credentials
- Sign in and redirect back to the demo application
- Cache the results with
cy.session()
function loginViaAAD(username: string, password: string) {
cy.visit('http://localhost:3000/')
cy.get('button#signIn').click()
// 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.live.com',
{
args: {
password,
},
},
({ password }) => {
cy.get('input[type="password"]').type(password, {
log: false,
})
cy.get('input[type="submit"]').click()
cy.get('#idBtn_Back').click()
}
)
// Ensure Microsoft has redirected us back to the sample app with our logged in user.
cy.url().should('equal', 'http://localhost:3000/')
cy.get('#welcome-div').should(
'contain',
`Welcome ${Cypress.env('aad_username')}!`
)
}
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
const log = Cypress.log({
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
})
log.snapshot('before')
loginViaAAD(username, password)
log.snapshot('after')
log.end()
})
Now, we can use our loginToAAD
command in the test. Below is our test to login
as a user via Azure Active Directory and run a basic sanity check.
describe('Azure Active Directory Authentication', () => {
beforeEach(() => {
// log into Azure Active Directory through our sample SPA using our custom command
cy.loginToAAD(Cypress.env('aad_username'), Cypress.env('aad_password'))
cy.visit('http://localhost:3000')
})
it('verifies the user logged in has the correct name', () => {
cy.get('#table-body-div td:contains("name") + td').should(
'contain',
`${Cypress.env('aad_name')}`
)
})
it('verifies the user logged in has the correct preferred name', () => {
cy.get('#table-body-div td:contains("preferred_username") + td').should(
'contain',
`${Cypress.env('aad_username')}`
)
})
})
We now have working authentication and tests! But we are logging in before every test, which is not only time consuming, but also can lead to API rate limiting due to the number of requests.
For this, we can refactor our login command to take advantage of
cy.session()
to store our logged in user's tokens
and/or cookies so we don't have to reauthenticate before every test.
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
cy.session(
`aad-${username}`,
() => {
const log = Cypress.log({
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
// @ts-ignore
autoEnd: false,
})
log.snapshot('before')
loginViaAAD(username, password)
log.snapshot('after')
log.end()
},
{
validate: () => {
// this is a very basic form of session validation for this demo.
// depending on your needs, something more verbose might be needed
cy.visit('http://localhost:3000')
cy.get('#welcome-div').should(
'contain',
`Welcome ${Cypress.env('aad_username')}!`
)
},
}
)
})
With the use of cy.session()
, our tests should now
run quicker!
We hope this guide was able to get you up and running with
cy.origin()
and
cy.session()
. If you ran into any issues while
following this guide, or have any feedback, please let us know and submit a
Github issue.
Happy testing!