Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cypress with Azure AD (MSAL)

I'm new to both Cypress and Azure AD, but I've been following the steps described here to create Cypress tests on an existing Angular app that uses Azure AD. It mentions that they are using ADAL, but our app uses MSAL, which it says should be similar. However, I'm struggling to get it to work. Here's my login function so far:

const tenant = 'https://login.microsoftonline.com/{my_tenant_id}/';
const tenantUrl = `${tenant}oauth2/token`;
const clientId = '{my_app_id}';
const clientSecret = '{my_secret}';
const azureResource = 'api://{my_app_id}';
const knownClientApplicationId = '{client_application_id_from_manifest}';
const userId = '{user_identifier}';
    
export function login() {
    cy.request({
        method: 'POST',
        url: tenantUrl,
        form: true,
        body: {
            grant_type: 'client_credentials',
            client_id: clientId,
            client_secret: clientSecret,
            resource: azureResource
        }
    }).then(response => {
        const Token = response.body.access_token;
        const ExpiresOn = response.body.expires_on;
        const key = `{"authority":"${tenant}","clientId":"${knownClientApplicationId}","scopes":${knownClientApplicationId},"userIdentifier":${userId}}`;
        const authInfo = `{"accessToken":"${Token}","idToken":"${Token}","expiresIn":${ExpiresOn}}`;
    
        window.localStorage.setItem(`msal.idtoken`, Token);
        window.localStorage.setItem(key, authInfo);
}
Cypress.Commands.add('login', login);

When I run this, an access token is returned. When I examine the local storage after a normal browser request, it has many more fields, such as msal.client.info (the authInfo value in the code above should also contain this value), but I've no idea where to get this information from.

The end result is that the POST request seems to return successfully, but the Cypress tests still consider the user to be unauthenticated.

The existing app implements a CanActivate service that passes if MsalService.getUser() returns a valid user. How can I convince this service that my Cypress user is valid?

Update:

After some experimentation with the local storage values, it looks like only two values are required to get past the login:

msal.idtoken
msal.client.info

The first I already have; the second one I'm not sure about, but it appears to return the same value every time. For now, I'm hard coding that value into my tests, and it seems to work somewhat:

then(response => {
    const Token = response.body.access_token;

    window.localStorage.setItem(`msal.idtoken`, Token);
    window.localStorage.setItem(`msal.client.info`, `{my_hard_coded_value}`);
});

The only minor issue now is that the MsalService.getUser() method returns slightly different values than the app is expecting (e.g. displayableId and name are missing; idToken.azp and idToken.azpacr are new). I'll investigate further...

like image 473
Michael McMullin Avatar asked Sep 26 '20 07:09

Michael McMullin


2 Answers

This solution got me a successful request but I still couldn't get past the login screen on my app, after some searching I found this great tutorial video that got me past the login screen! I am using the msal-react & msal-browser npm packages.

https://www.youtube.com/watch?v=OZh5RmCztrU

Repo for the code is here:

https://github.com/juunas11/AzureAdUiTestAutomation/tree/main/UiTestAutomation.Cypress/cypress

like image 84
Jack David Evans Avatar answered Oct 12 '22 01:10

Jack David Evans


I want to thank you for all of the groundwork you've done to figure out which variables to set when working with MSAL! I think I can help with figuring out where clientInfo comes from. It looks like it is generated from the clientId, which explains why it is always the same value:

static createClientInfoFromIdToken(idToken:IdToken, authority: string): ClientInfo {
        const clientInfo = {
            uid: idToken.subject, 
            utid: ""
        };

        return new ClientInfo(CryptoUtils.base64Encode(JSON.stringify(clientInfo)), authority);
    }

See source here: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/70b87bcd27bfe67789c92b9bc046b45733f490ed/lib/msal-core/src/ClientInfo.ts

I was able to use your code and just added import * as MSAL from "@azure/msal-browser" and window.localStorage.setItem(`msal.client.info`, MSAL.clientInfo);

Worked like a charm for me!

like image 36
Kimberly Ly Avatar answered Oct 11 '22 23:10

Kimberly Ly