Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(auth): allow usage with iframe #1944

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/afraid-countries-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@equinor/fusion-framework-cli": patch
---

Updated CLI config to allow embedding in iframe
15 changes: 15 additions & 0 deletions .changeset/four-waves-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@equinor/fusion-framework-module-msal": minor
---

Added functionality for setting default authentication behavior (`redirect` or `popup`) when creating authentication client.

```ts
const client = new AuthClient(
"TENNANT_ID",
{
/** auth client options */
},
"redirect" || "popup",
);
```
5 changes: 5 additions & 0 deletions .changeset/plenty-moose-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@equinor/fusion-framework-cookbook-app-react": patch
---

Added sample page for testing og iframe
10 changes: 10 additions & 0 deletions .changeset/real-houses-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@equinor/fusion-framework-module-msal": minor
---

Changed `handleRedirect` to check the state of `handleRedirectPromise`, since login threw popup should not trigger redirect.

Added flag in login state

- `internal` [_default_] when login was triggered from same frame
- `external` login was triggered by a iframe
10 changes: 10 additions & 0 deletions cookbooks/app-react/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>My Web Page</title>
</head>
<body>
<h1>Hello, world!</h1>
<iframe src="http://localhost:3000/apps/fusion-framework-cookbook-app-react?behaviour=popup" width="100%" height="800px"></iframe>
</body>
</html>
3 changes: 3 additions & 0 deletions packages/cli/src/bin/dev-portal/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const configure = async (config: FrameworkConfigurator) => {
tenantId: '3aa4a235-b6e2-48d5-9195-7fcf05b459b0',
clientId: '9b707e3a-3e90-41ed-a47e-652a1e3b53d0',
redirectUri: '/authentication/login-callback',
config: {
behavior: window.parent === window ? 'redirect' : 'popup',
},
},
{ requiresAuth: true },
);
Expand Down
8 changes: 6 additions & 2 deletions packages/modules/msal/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,19 @@ export class AuthClient extends PublicClientApplication {
return this.browserStorage.getTemporaryCache('request.origin', true);
}

#behavior: AuthBehavior;

/**
* @param tenantId - tenant id for client domain
* @param config - required [Configuration](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/src/config/Configuration.ts)
*/
constructor(
readonly tenantId: string,
config: Configuration,
behavior: AuthBehavior = defaultBehavior,
) {
super(config);
this.#behavior = behavior;
}

/**
Expand All @@ -109,7 +113,7 @@ export class AuthClient extends PublicClientApplication {
*/
async login(
options?: AuthRequest,
behavior: AuthBehavior = defaultBehavior,
behavior: AuthBehavior = this.#behavior,
silent = true,
): Promise<AuthenticationResult | void> {
const loginHint = options?.loginHint || this.account?.username;
Expand Down Expand Up @@ -153,7 +157,7 @@ export class AuthClient extends PublicClientApplication {
*/
public async acquireToken(
options: AuthRequest = { scopes: [] },
behavior: AuthBehavior = defaultBehavior,
behavior: AuthBehavior = this.#behavior,
silent = true,
): Promise<AuthenticationResult | void> {
const account = await this.account;
Expand Down
12 changes: 7 additions & 5 deletions packages/modules/msal/src/client/create-auth-client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Configuration, IPublicClientApplication } from '@azure/msal-browser';
import { AuthClient } from './client';
import { normalizeUri } from './util/url';
import { AuthBehavior } from './behavior';

export type AuthClientConfig = Configuration & {
auth: Partial<Configuration['auth']>;
export type AuthClientConfig = Omit<Configuration, 'auth'> & {
auth?: Partial<Configuration['auth']>;
behavior?: AuthBehavior;
};

/**
Expand Down Expand Up @@ -31,7 +33,7 @@ export const createAuthClient = <T extends IPublicClientApplication = AuthClient
clientId: string,
redirectUri?: string,
config?: AuthClientConfig,
ctor?: new (tenantId: string, config: Configuration) => T,
ctor?: new (tenantId: string, config: Configuration, defaultBehavior?: AuthBehavior) => T,
): T => {
const auth: Configuration['auth'] = {
clientId,
Expand All @@ -41,8 +43,8 @@ export const createAuthClient = <T extends IPublicClientApplication = AuthClient
...config?.auth,
};
const cache = { cacheLocation: 'localStorage', ...config?.cache };
const system = config?.system;
return new (ctor || AuthClient)(tenantId, { auth, cache, system }) as T;
const { behavior, system } = config ?? {};
return new (ctor || AuthClient)(tenantId, { auth, cache, system }, behavior) as T;
};

export default createAuthClient;
15 changes: 12 additions & 3 deletions packages/modules/msal/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,19 @@ export class AuthProvider implements IAuthProvider {
const logger = client.getLogger();
const { requestOrigin } = client;

await client.handleRedirectPromise();
if (requestOrigin === redirectUri) {
const res = await client.handleRedirectPromise();

// if not internal state, do not redirect (state will not be included when popup was created from iframe)
if (res?.state !== 'internal') {
return null;
} else if (requestOrigin === redirectUri) {
// prevent redirect loop
logger.warning(
`detected callback loop from url ${redirectUri}, redirecting to root`,
);
window.location.replace('/');
} else {
// either redirect to origin or root
window.location.replace(requestOrigin || '/');
}
}
Expand All @@ -121,6 +127,9 @@ export class AuthProvider implements IAuthProvider {
}

async login(): Promise<void> {
await this.defaultClient.login();
await this.defaultClient.login({
state: window.parent === window ? 'internal' : 'external',
scopes: [],
});
}
}
Loading