Merge pull request #20694 from afscrome/entra-domain-hint
Add `domain_hint` support to Entra ID login
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend-module-microsoft-provider': patch
|
||||
---
|
||||
|
||||
Added support for specifying a domain hint on the Microsoft authentication provider configuration.
|
||||
@@ -38,13 +38,18 @@ auth:
|
||||
clientId: ${AUTH_MICROSOFT_CLIENT_ID}
|
||||
clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET}
|
||||
tenantId: ${AUTH_MICROSOFT_TENANT_ID}
|
||||
domainHint: ${AZURE_TENANT_ID}
|
||||
```
|
||||
|
||||
The Microsoft provider is a structure with three configuration keys:
|
||||
The Microsoft provider is a structure with three mandatory configuration keys:
|
||||
|
||||
- `clientId`: Application (client) ID, found on App Registration > Overview
|
||||
- `clientSecret`: Secret, found on App Registration > Certificates & secrets
|
||||
- `tenantId`: Directory (tenant) ID, found on App Registration > Overview
|
||||
- `domainHint` (optional): Typically the same as `tenantId`.
|
||||
Leave blank if your app registration is multi tenant.
|
||||
When specified, this reduces login friction for users with accounts in multiple tenants by automatically filtering away accounts from other tenants.
|
||||
For more details, see [Home Realm Discovery](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/home-realm-discovery-policy)
|
||||
|
||||
## Outbound Network Access
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ export const authModuleMicrosoftProvider: () => BackendFeature;
|
||||
|
||||
// @public (undocumented)
|
||||
export const microsoftAuthenticator: OAuthAuthenticator<
|
||||
PassportOAuthAuthenticatorHelper,
|
||||
{
|
||||
helper: PassportOAuthAuthenticatorHelper;
|
||||
domainHint: string | undefined;
|
||||
},
|
||||
PassportProfile
|
||||
>;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface Config {
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
domainHint?: string;
|
||||
callbackUrl?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -30,8 +30,9 @@ export const microsoftAuthenticator = createOAuthAuthenticator({
|
||||
const clientId = config.getString('clientId');
|
||||
const clientSecret = config.getString('clientSecret');
|
||||
const tenantId = config.getString('tenantId');
|
||||
const domainHint = config.getOptionalString('domainHint');
|
||||
|
||||
return PassportOAuthAuthenticatorHelper.from(
|
||||
const helper = PassportOAuthAuthenticatorHelper.from(
|
||||
new MicrosoftStrategy(
|
||||
{
|
||||
clientID: clientId,
|
||||
@@ -55,19 +56,30 @@ export const microsoftAuthenticator = createOAuthAuthenticator({
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
helper,
|
||||
domainHint,
|
||||
};
|
||||
},
|
||||
|
||||
async start(input, helper) {
|
||||
return helper.start(input, {
|
||||
async start(input, ctx) {
|
||||
const options: Record<string, string> = {
|
||||
accessType: 'offline',
|
||||
});
|
||||
};
|
||||
|
||||
if (ctx.domainHint !== undefined) {
|
||||
options.domain_hint = ctx.domainHint;
|
||||
}
|
||||
|
||||
return ctx.helper.start(input, options);
|
||||
},
|
||||
|
||||
async authenticate(input, helper) {
|
||||
return helper.authenticate(input);
|
||||
async authenticate(input, ctx) {
|
||||
return ctx.helper.authenticate(input);
|
||||
},
|
||||
|
||||
async refresh(input, helper) {
|
||||
return helper.refresh(input);
|
||||
async refresh(input, ctx) {
|
||||
return ctx.helper.refresh(input);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ import request from 'supertest';
|
||||
import { authModuleMicrosoftProvider } from './module';
|
||||
|
||||
describe('authModuleMicrosoftProvider', () => {
|
||||
it('should start', async () => {
|
||||
it('should start without domain hint', async () => {
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
authPlugin,
|
||||
@@ -77,4 +77,63 @@ describe('authModuleMicrosoftProvider', () => {
|
||||
nonce: decodeURIComponent(nonceCookie.value),
|
||||
});
|
||||
});
|
||||
|
||||
it('should start with domain hint', async () => {
|
||||
const { server } = await startTestBackend({
|
||||
features: [
|
||||
authPlugin,
|
||||
authModuleMicrosoftProvider,
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
app: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
},
|
||||
auth: {
|
||||
providers: {
|
||||
microsoft: {
|
||||
development: {
|
||||
clientId: 'another-client-id',
|
||||
clientSecret: 'another-client-secret',
|
||||
tenantId: 'another-tenant-id',
|
||||
domainHint: 'somedomain',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const agent = request.agent(server);
|
||||
|
||||
const res = await agent.get('/api/auth/microsoft/start?env=development');
|
||||
|
||||
expect(res.status).toEqual(302);
|
||||
|
||||
const nonceCookie = agent.jar.getCookie('microsoft-nonce', {
|
||||
domain: 'localhost',
|
||||
path: '/api/auth/microsoft/handler',
|
||||
script: false,
|
||||
secure: false,
|
||||
});
|
||||
expect(nonceCookie).toBeDefined();
|
||||
|
||||
const startUrl = new URL(res.get('location'));
|
||||
expect(startUrl.origin).toBe('https://login.microsoftonline.com');
|
||||
expect(startUrl.pathname).toBe('/another-tenant-id/oauth2/v2.0/authorize');
|
||||
expect(Object.fromEntries(startUrl.searchParams)).toEqual({
|
||||
response_type: 'code',
|
||||
scope: 'user.read',
|
||||
client_id: 'another-client-id',
|
||||
redirect_uri: `http://localhost:${server.port()}/api/auth/microsoft/handler/frame`,
|
||||
state: expect.any(String),
|
||||
domain_hint: 'somedomain',
|
||||
});
|
||||
|
||||
expect(decodeOAuthState(startUrl.searchParams.get('state')!)).toEqual({
|
||||
env: 'development',
|
||||
nonce: decodeURIComponent(nonceCookie.value),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user