Office Word add-in – Msal using login redirect not working on Mac (Safari webkit 1.0) - safari

We’re using Azure B2C to authenticate the users in the Word add-in. The library in Angular (14.1.2) that we use is MSAL which works as expected on Windows.
However, when using it on Mac, the redirect inside the add-in dialog gets stuck and doesn’t go to the Azure B2C page. It writes the cookies and local storage, but never redirects.
angular 14.1.2
azure/msal-angular 2.5.1
azure/msal-browser 2.32.1
Call
this.msalService.loginRedirect().subscribe();
Msal Configuration
`export function msalInstanceFactory(appConfigService: AppConfigService): IPublicClientApplication {
const appConfig = appConfigService.config;
return new PublicClientApplication({
auth: {
clientId: appConfig.aadB2CClientId,
authority: 'https://${appConfig.aadB2CDomain}/${appConfig.aadB2CTenant}/${appConfig.aadB2CSIPolicy}',
redirectUri: appConfig.aadB2CRedirectUri,
postLogoutRedirectUri: getLocationOriginWithPath('logout'),
knownAuthorities: [appConfig.aadB2CDomain],
navigateToLoginRequestUrl: false,
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE,
},
system: {
loggerOptions: {
loggerCallback: msalLoggerCallback,
logLevel: LogLevel.Verbose,
piiLoggingEnabled: true,
},
},
});
}
export function msalGuardConfigFactory(appConfigService: AppConfigService): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: appConfigService.config.aadB2CConsentScopes,
loginHint: getLoginHint(),
},
};
}`
The funny fact is that if I set a break point just before the execution of the loginRedirect and then press continue it works. So, I tried to set a timeout before that to see if it works but it doesn't.
Also tried to disable ITP security in Safari, but didn't take any effect.

Related

Playwright: unable to login via API setting cookie (able to do it with Cypress)

I'm trying to implemented login via API following Playwright's guidelines but somehow nothing seems to be working.
As a comparison I've built the same in Cypress and it works out of the box:
Context:
Playwright Version: 1.30
Operating System: Mac
Node.js version: v16.19.0
Browser: Chromium
I am unable to make a simple API login that works perfectly using Cypress instead. Let me share the 2 code snippets for comparison:
Simple test case:
API request to the login end-point - Auth token is retrieved
set the auth token as a cookie
navigate to a page that is accessible only if authenticated
Code Snippet
Cypress (working fine)
const body = {
username: 'username...',
password: 'password',
rememberMe: true,
};
describe('Login via API to management console', () => {
it('Login via API to management console', () => {
cy.request({
method: 'POST',
url: loginEndPoint,
headers: {
'Content-Type': 'application/json',
},
body,
}).then((response) => {
cy.setCookie('Authorization', `Token ${response.body.data.token}`);
});
cy.visit(`/management`);
});
});
Playwright (not working)
test('Login via API', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
const loginResponse = await context.request.post(`https://${process.env.MANAGEMENT_URL}/web/api/v2.1/users/login`, {
data: {
username: process.env.MANAGEMENT_USER,
password: process.env.MANAGEMENT_PASSWORD,
rememberMe: true,
}
});
const {
data: { token },
} = await loginResponse.body().then((b) => {
return JSON.parse(b.toString());
});
expect(token).toMatch(/^[a-z0-9]{80}$/)
await context.addCookies([{ name: 'Authorization', value: `Token ${token}`, path: '/', domain: `https://${process.env.MANAGEMENT_URL}` }]);
await page.goto(`https://${process.env.MANAGEMENT_URL}/management/`);
await expect(page).toHaveURL(/management/);
});
Describe the bug
Both scripts are successful at retrieving the authentication token but somehow either I'm doing something wrong with setting the cookie in Playwright or there is an issue. I'd assume the 2 scripts should be comparable.
Furthermore: I've tried to execute login via UI using global-setup, saving the storage-state, loading it before running the test and it fails also in this case... so there is something that is not setting properly the state in this case or the cookie in the previous one.
Not entirely sure why the cookie approach wasn’t working, perhaps the https:// part should be removed from the domain?
That being said, in Playwright you shouldn’t even need to do that especially within a single test, looking at the Playwright docs on signing in via the API and related page about the request context particularly under cookie management. The associated request and browser contexts share cookies, so once you complete the login request, the browser should already have the cookie state too and be logged in, so you should be able to just remove getting the token and adding the cookie. Or you can login with the API in the global setup even, as that doc showed. Just make sure in that case to save the storage state, and specify the same file in your config.
I see you tried the global setup approach (through the UI, but you can use the API since you have it), not sure what happened there. I would say to ensure that you specified the storageState in the config; I would be curious how you loaded it as mentioned, and if you’re still having problems maybe share the code you’re using for that piece?
Hope that helps or we can troubleshoot further!

Playwright - Cookies not set from storageState file from different domains in Chromium

Concept
I'm using the concept of reusing the authentication state via storageState file in Playwright.
Following is my code snippet spread across different files:
Code
playwright.config.json
import type { PlaywrightTestConfig } from '#playwright/test';
import { devices } from '#playwright/test';
const config: PlaywrightTestConfig = {
testDir: './e2e',
reporter: 'html',
globalSetup: require.resolve('./e2e/global-setup.ts'),
use: {
storageState: './e2e/authStorageState.json',
},
projects: [
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
},
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
},
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
};
export default config;
global-setup.ts
require('dotenv').config();
import { firefox, FullConfig } from '#playwright/test';
import E2EConstants from './e2e.constants';
const authenticateUser = async () => {
const browser = await firefox.launch();
const page = await browser.newPage();
await page.goto(`${process.env.BASE_URL}/${E2EConstants.LoginPage.URL}`);
await page
.getByLabel(E2EConstants.LoginPage.LABEL.EMAIL)
.fill(process.env.TEST_ADMIN_USERNAME as string);
await page
.getByLabel(E2EConstants.LoginPage.LABEL.PASSWORD)
.fill(process.env.TEST_ADMIN_PASSWORD as string);
await page.getByText(E2EConstants.LoginPage.BUTTON).click();
await page.waitForLoadState('networkidle');
await page.context().storageState({ path: './e2e/authStorageState.json' });
await browser.close();
};
async function globalSetup(_: FullConfig) {
await authenticateUser();
}
export default globalSetup;
This sets all the cookies in the authStorageState.json file. However, some cookies have domain as .b.com and some of them have the domain as a.b.com.
example.spec.ts
require('dotenv').config();
import { test, expect } from '#playwright/test';
test('homepage has same link', async ({ page }) => {
await page.goto(process.env.TEST_URL as string);
await expect(page).toHaveURL(process.env.TEST_URL as string);
});
The TEST_URL is the URL with domain a.b.com that is behind authentication and is accessible only when the user is signed in.
Problem
When I run the tests, I see that the Chromium test fails but the Firefox and Webkit tests pass. The test is unable to sign the user in on Chromium, unlike on other browsers. This is because the auth-related cookies (belonging to a.b.com domain) are not set on Chromium but they are set on other browsers. However, the non-auth-related cookies (belonging to .b.com domain) are set properly on all browsers.
What I tried
I tried manually setting the url of the cookies saved in authStorageState.json file to https://a.b.com after deleting the domain and path keys for the auth-related cookies and then when I run the tests, the Chromium test also passes.
The secure key for all cookies, which were not set, had the value as false. I tried manually changing the "secure": true for all cookies which were not set. Note that the sameSite property is set to "None". This change made the Chromium test cases to pass.
The sameSite key for all cookies, which were not set, had the value "None" while having "secure": false property. I tried manually removing the "sameSite": "None" property for all cookies which were not set. Note that I did not change the secure property. This change made the Chromium test cases to pass.
[Note that all the above points were independently implemented as workarounds and were not done together.]
Requirement
However, because the authStorageState.json file is to be created by default on first sign-in, I want to persist the same cookies that appear on browser instead of manually manipulating them. How can I get my Chromium test cases to pass?
Doubts
What is the difference between providing url instead of domain + path for Chromium?
Why do the cookies with subdomain a.b.com require secure property to be set to true and the Chromium-based test cases pass after this change?
Is removing the "sameSite": "None" for all the cookies with subdomain a.b.com right and why do the Chromium-based test cases pass after this change?

Does app check check work with firebase phone auth?

I'm new to web development in general. started learning Javascript last year.
I created a website for testing. Before implementing app check, phone auth worked fine.
I'm using reCaptcha enterprise for app check.
I get this error: Uncaught (in promise) TypeError: recaptchaVerifier.render is not a function
implementing app check:
const { initializeAppCheck, ReCaptchaEnterpriseProvider } = require("firebase/app-check");
const appCheck = initializeAppCheck(firebaseApp, {
provider: new ReCaptchaEnterpriseProvider('**********************************'),
isTokenAutoRefreshEnabled: true // Set to true to allow auto-refresh.
});
My javascript code for implementing phone auth:
$('#phone-method').click(function() {
window.recaptchaVerifier = new RecaptchaVerifier('recaptcha-container', {}, auth);
// Sign in with phone flow
})
Apparently, there was a problem with enterprise but they have since fixed it:
https://github.com/firebase/firebase-js-sdk/issues/6133
Same problem to me, but this thread in Github help me:
https://github.com/firebase/firebase-js-sdk/issues/6133
Here you have a nice sample inside.
It seems that appCheck only works great with ReCaptchaV3Provider. You will just have to change:
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(*****),
isTokenAutoRefreshEnabled: true
})
to:
initializeAppCheck(app, {
provider: new ReCaptchaV3Provider(*****),
isTokenAutoRefreshEnabled: true
})
And I recommend to change the way to initialize captcha, set to invisible like this:
window.recaptchaVerifier = new RecaptchaVerifier('recaptcha-container',
{ 'size': 'invisible' },
auth);

Nuxt auth with keycloak: ssr this.$auth.loggedIn always false on page load

I have a setup with nuxt and keycloak as auth strategy which in general is working. I can login via keycloak and then will have this.$auth.loggedIn === true on the page. When navigating via vue-router, this.$auth.loggedIn will also be true when switching to a new page.
But when I then reload the page (CMD+r/F5), server side rendering will have false for this.$auth.loggedIn, while on client side it will be true. This forced me to do a lot of <client-only> blocks in the templates to prevent ssr mismatches.
I wonder if it is possible that on first page load server side rendering can return a page with authorized content? I would think this should be possible since cookies with auth info are set and sent to the server.
Or is that never possible and efficient server side rendering can only be used for non-authorized content?
Versions:
nuxt: 2.15.8
#nuxtjs/auth-next: 5.0.0-1643791578.532b3d6
nuxt.config.js:
auth: {
strategies: {
keycloak: {
scheme: 'oauth2',
endpoints: {
authorization: `${ process.env.KEYCLOAK }/protocol/openid-connect/auth`,
userInfo: `${ process.env.KEYCLOAK }/protocol/openid-connect/userinfo`,
token: `${ process.env.KEYCLOAK }/protocol/openid-connect/token`,
logout: `${ process.env.KEYCLOAK }/protocol/openid-connect/logout`,
},
token: {
property: 'access_token',
type: 'Bearer',
maxAge: 1800,
},
refreshToken: {
property: 'refresh_token',
maxAge: 60 * 60 * 24 * 30,
},
responseType: 'code',
grantType: 'authorization_code',
clientId: process.env.CLIENT_ID,
scope: ['openid', 'profile', 'email', 'roles'],
codeChallengeMethod: 'S256',
redirect: {
logout: '/',
callback: '/',
home: '/',
},
},
},
},
Having a Vue component with this:
created() {
console.log(this.$auth.loggedIn);
},
Will return false for SSR and true on client side on page load/refresh when logged in.
After manually implementing a server side authenticator, I found out that the problem was my local docker setup.
Didn't think this was the problem before, so I forgot to mention it.
I have a local docker container with keycloak and a local docker container with nuxt.
Long story short, it seems that the nuxt server wasn't able to communicate with keycloak, hence wasn't able to fetch the user. After changing some addresses so that keycloak was available on the same address from the browser and from within my nuxt server docker container, the nuxt server did get $auth.loggedIn=true automatically on page load if the is was logged in.
Not sure if I didn't see it, but I wished nuxt auth would give me an error if the nuxt server failed to communicate with the authorization server. Would have saved me a lot of debugging.

nuxtjs/google-tag-manager only add google analytics when cookie consent has been given

I'm currently trying to make my Tracking Opt-In. Meaning that before Google Analytics tracks the user, the user has to give consent. My website uses Nuxtjs and the #nuxtjs/google-tag-manager. In the nuxt-config.js I set it up like this modules: [
'nuxt-leaflet',
'#nuxtjs/redirect-module',
['#nuxtjs/moment', { locales: ['de'], plugin: false }],
['#nuxtjs/sitemap', {
path: '/sitemap.xml',
generate: false,
cacheTime: (1000 * 60 * 60), // generate every hour
gzip: true,
hostname: 'https://kreuzwerker.de',
routes () {
return generateSitemap()
}
}],
['#nuxtjs/google-tag-manager',
{
id: '/*GTM-Code*/',
dev: true // to disable in dev mode
}]
],
and it works perfectly fine but now I want to connect it somehow to my Cookie Consent Form. How would I do that?
First of all, you're using a deprecated nuxtjs module; use the new GTM module, right here; https://github.com/nuxt-community/gtm-module.
In your config, make sure you've set the following:
gtm: {
autoInit: false
}
Once you've gotten consent, in your form, call a callback function which calls the GTM init function; $gtm.init('GTM-XXXXXXX').
Good luck, read the GitHub page it explains it all.