Use additionalScopes for Okta auth instead of overriding scope entirely

Signed-off-by: ataylorme <andrew@ataylor.me>
This commit is contained in:
ataylorme
2023-10-26 05:29:03 -07:00
parent 1185e30cb4
commit 8466307819
5 changed files with 29 additions and 21 deletions
+7 -7
View File
@@ -2,7 +2,7 @@
'@backstage/plugin-auth-backend': minor
---
Allow user-defined scopes for Okta auth in config yaml
Allow additional user-defined scopes for Okta auth in config yaml
Example `app-config.yaml` excerpt
@@ -18,11 +18,11 @@ auth:
authServerId: ${AUTH_OKTA_AUTH_SERVER_ID} # Optional
idp: ${AUTH_OKTA_IDP} # Optional
# https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned
scope: openid profile email offline_access groups # Optional
additionalScopes: groups # Optional
```
- Accept a new scope option during okta creation with `createAuthProviderIntegration`
- Pass the user-defined `scope` as an option to `OktaAuthProvider`
- Add `scope` as an option for `OktaAuthProvider`
- Set `scope` in `OktaAuthProvider` to the `scope` passed as an `option` or a default of `'openid email profile offline_access'` if a user-defined option is not provided
- Update the `start` and `refresh` methods to use `scope` from `OktaAuthProvider` rather than `scope` from the request
- Accept a new additionalScope option during okta creation with `createAuthProviderIntegration`
- Passes the the user-defined `additionalScopes` as an option to `OktaAuthProvider`
- Add `additionalScopes` as an option for `OktaAuthProvider`
- Set `scope` in `OktaAuthProvider` to the combined value of current scopes combined with the user-defined `additionalScopes` passed as an `option`
- Update the `start` and `refresh` methods to use the new combiend `scope` from `OktaAuthProvider` rather than `scope` from the request
+2 -2
View File
@@ -44,7 +44,7 @@ auth:
authServerId: ${AUTH_OKTA_AUTH_SERVER_ID} # Optional
idp: ${AUTH_OKTA_IDP} # Optional
# https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned
scope: ${AUTH_OKTA_SCOPE} # Optional
additionalScopes: ${AUTH_OKTA_ADDITIONAL_SCOPES} # Optional
```
The values referenced are found on the Application page on your Okta site.
@@ -57,7 +57,7 @@ The values referenced are found on the Application page on your Okta site.
- `authServerId`: The authorization server ID for the Application
- `idp`: The identity provider for the application, e.g. `0oaulob4BFVa4zQvt0g3`
`scope` is an optional value to change the `scope` value sent to Okta during OAuth. The default value is `openid profile email offline_access`. Changing the `scope` will have an impact on [the dependent claims returned](https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned). For example, adding `groups` to the `scope` by setting the value to `openid profile email offline_access groups` will result in the claim returning a list of the groups that the user is a member of that also match the ID token group filter of the client app.
`additionalScopes` is an optional value, a string of space separated scopes, that will be combined with the default `scope` value of `openid profile email offline_access` to adjust the `scope` sent to Okta during OAuth. This will have an impact on [the dependent claims returned](https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned). For example, setting the `additionalScopes` value to `groups` will result in the claim returning a list of the groups that the user is a member of that also match the ID token group filter of the client app.
## Adding the provider to the Backstage frontend
+1
View File
@@ -131,6 +131,7 @@ export interface Config {
authServerId?: string;
idp?: string;
callbackUrl?: string;
additionalScopes?: string;
};
};
/** @visibility frontend */
@@ -105,6 +105,7 @@ describe('createOktaProvider', () => {
});
it('should pass a custom scope to start and refresh requests', async () => {
const additionalScopes = 'groups';
const mockScope = 'openid profile email offline_access groups';
const reqScope = 'openid profile email offline_access';
const provider = new OktaAuthProvider({
@@ -119,7 +120,7 @@ describe('createOktaProvider', () => {
clientId: 'mock',
clientSecret: 'mock',
callbackUrl: 'mock',
scope: mockScope,
additionalScopes,
});
mockRedirectStrategy.mockResolvedValueOnce({
@@ -60,7 +60,7 @@ export type OktaAuthProviderOptions = OAuthProviderOptions & {
signInResolver?: SignInResolver<OAuthResult>;
authHandler: AuthHandler<OAuthResult>;
resolverContext: AuthResolverContext;
scope?: string;
additionalScopes?: string;
};
export class OktaAuthProvider implements OAuthHandlers {
@@ -68,7 +68,7 @@ export class OktaAuthProvider implements OAuthHandlers {
private readonly signInResolver?: SignInResolver<OAuthResult>;
private readonly authHandler: AuthHandler<OAuthResult>;
private readonly resolverContext: AuthResolverContext;
private readonly scope: string;
private readonly additionalScopes: string;
/**
* Due to passport-okta-oauth forcing options.state = true,
@@ -91,7 +91,7 @@ export class OktaAuthProvider implements OAuthHandlers {
this.signInResolver = options.signInResolver;
this.authHandler = options.authHandler;
this.resolverContext = options.resolverContext;
this.scope = options.scope || 'openid email profile offline_access';
this.additionalScopes = options.additionalScopes || '';
this.strategy = new OktaStrategy(
{
@@ -128,11 +128,19 @@ export class OktaAuthProvider implements OAuthHandlers {
);
}
private combineScopeStrings(scopesA: string, scopesB: string) {
const scopesAArray = scopesA.split(' ');
const scopesBArray = scopesB.split(' ');
const combinedScopes = new Set([...scopesAArray, ...scopesBArray]);
return Array.from(combinedScopes).join(' ');
}
async start(req: OAuthStartRequest): Promise<OAuthStartResponse> {
const scope = this.combineScopeStrings(req.scope, this.additionalScopes);
return await executeRedirectStrategy(req, this.strategy, {
accessType: 'offline',
prompt: 'consent',
scope: this.scope,
scope: scope,
state: encodeState(req.state),
});
}
@@ -150,12 +158,9 @@ export class OktaAuthProvider implements OAuthHandlers {
}
async refresh(req: OAuthRefreshRequest) {
const scope = this.combineScopeStrings(req.scope, this.additionalScopes);
const { accessToken, refreshToken, params } =
await executeRefreshTokenStrategy(
this.strategy,
req.refreshToken,
this.scope,
);
await executeRefreshTokenStrategy(this.strategy, req.refreshToken, scope);
const fullProfile = await executeFetchUserProfileStrategy(
this.strategy,
@@ -233,7 +238,8 @@ export const okta = createAuthProviderIntegration({
const callbackUrl =
customCallbackUrl ||
`${globalConfig.baseUrl}/${providerId}/handler/frame`;
const scope = envConfig.getOptionalString('scope');
const additionalScopes =
envConfig.getOptionalString('additionalScopes');
// This is a safe assumption as `passport-okta-oauth` uses the audience
// as the base for building the authorization, token, and user info URLs.
@@ -258,7 +264,7 @@ export const okta = createAuthProviderIntegration({
authHandler,
signInResolver: options?.signIn?.resolver,
resolverContext,
scope,
additionalScopes,
});
return OAuthAdapter.fromConfig(globalConfig, provider, {