Skip to content

Commit f1d3587

Browse files
authored
add additional OIDC auth resolvers (#2020)
Signed-off-by: Jessica He <[email protected]>
1 parent ef10f2e commit f1d3587

File tree

6 files changed

+88
-0
lines changed

6 files changed

+88
-0
lines changed

docs/auth.md

+2
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ For more information on setting up the OAuth2 Proxy auth provider, consult the [
106106
# - resolver: preferredUsernameMatchingUserEntityName
107107
# - resolver: emailMatchingUserEntityProfileEmail
108108
# - resolver: emailLocalPartMatchingUserEntityName
109+
# - resolver: oidcSubClaimMatchingKeycloakUserId
109110
```
110111

111112
In an example using Keycloak for authentication with the OIDC provider, there are a few steps that need to be taken to get everything working:
@@ -122,6 +123,7 @@ In an example using Keycloak for authentication with the OIDC provider, there ar
122123
The default resolver provided by the `oidc` auth provider is the `emailLocalPartMatchingUserEntityName` resolver.
123124

124125
If you want to use a different resolver, add the resolver you want to use in the `auth.providers.oidc.[environment].signIn.resolvers` configuration as soon in the example above, and it will override the default resolver.
126+
* For enhanced security, consider using the `oidcSubClaimMatchingKeycloakUserId` resolver which matches the user with the immutable `sub` parameter from OIDC to the Keycloak user ID.
125127

126128
For more information on setting up the OIDC auth provider, consult the [Backstage documentation](https://backstage.io/docs/auth/oidc#the-configuration).
127129

docs/ping-identity-oidc-setup.md

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ auth:
3737
clientId: ${PING_IDENTITY_CLIENT_ID}
3838
clientSecret: ${PING_IDENTITY_CLIENT_SECRET}
3939
prompt: auto #optional
40+
signIn:
41+
resolvers:
42+
- resolver: oidcSubClaimMatchingPingIdentityUserId
4043
```
4144
4245
The OIDC provider requires three mandatory configuration keys:
@@ -46,6 +49,7 @@ The OIDC provider requires three mandatory configuration keys:
4649
- `metadataUrl`: Copy from `OIDC Discovery Endpoint` under `Configuration` tab in `URLs` drop down.
4750
- `prompt` (optional): Recommended to use auto so the browser will request login to the IDP if the user has no active session.
4851
- `additionalScopes` (optional): List of scopes for the App Registration, to be requested in addition to the required ones.
52+
- `signIn.resolvers.resolver` (optional): `oidcSubClaimMatchingPingIdentityUserId` is a secure user resolver that matches the `sub` claim from OIDC to the Ping Identity user ID.
4953

5054
#### Known Issues
5155

packages/backend/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@opentelemetry/sdk-node": "0.57.1",
6161
"app": "*",
6262
"global-agent": "3.0.0",
63+
"jose": "^5.9.6",
6364
"undici": "6.21.1",
6465
"winston": "3.14.2"
6566
},

packages/backend/src/modules/authProvidersModule.ts

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
createOAuthProviderFactory,
2525
} from '@backstage/plugin-auth-node';
2626

27+
import { rhdhSignInResolvers } from './authResolvers';
28+
2729
/**
2830
* Function is responsible for signing in a user with the catalog user and
2931
* creating an entity reference based on the provided name parameter.
@@ -221,6 +223,10 @@ function getAuthProviderFactory(providerId: string): AuthProviderFactory {
221223
signInResolver:
222224
oidcSignInResolvers.emailLocalPartMatchingUserEntityName(),
223225
signInResolverFactories: {
226+
oidcSubClaimMatchingKeycloakUserId:
227+
rhdhSignInResolvers.oidcSubClaimMatchingKeycloakUserId,
228+
oidcSubClaimMatchingPingIdentityUserId:
229+
rhdhSignInResolvers.oidcSubClaimMatchingPingIdentityUserId,
224230
...oidcSignInResolvers,
225231
},
226232
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { OidcAuthResult } from '@backstage/plugin-auth-backend-module-oidc-provider';
2+
import {
3+
AuthResolverContext,
4+
createSignInResolverFactory,
5+
OAuthAuthenticatorResult,
6+
SignInInfo,
7+
} from '@backstage/plugin-auth-node';
8+
9+
import { decodeJwt } from 'jose';
10+
11+
const KEYCLOAK_ID_ANNOTATION = 'keycloak.org/id';
12+
const PING_IDENTITY_ID_ANNOTATION = 'pingidentity.org/id';
13+
14+
/**
15+
* Creates an OIDC sign-in resolver that looks up the user using a specific annotation key.
16+
*
17+
* @param annotationKey - The annotation key to match the user's `sub` claim.
18+
* @param providerName - The name of the identity provider to report in error message if the `sub` claim is missing.
19+
*/
20+
const createOidcSubClaimResolver = (userIdKey: string, providerName: string) =>
21+
createSignInResolverFactory({
22+
create() {
23+
return async (
24+
info: SignInInfo<OAuthAuthenticatorResult<OidcAuthResult>>,
25+
ctx: AuthResolverContext,
26+
) => {
27+
const sub = info.result.fullProfile.userinfo.sub;
28+
if (!sub) {
29+
throw new Error(
30+
`The user profile from ${providerName} is missing a 'sub' claim, likely due to a misconfiguration in the provider. Please contact your system administrator for assistance.`,
31+
);
32+
}
33+
34+
const idToken = info.result.fullProfile.tokenset.id_token;
35+
if (!idToken) {
36+
throw new Error(
37+
`The user ID token from ${providerName} is missing a 'sub' claim, likely due to a misconfiguration in the provider. Please contact your system administrator for assistance.`,
38+
);
39+
}
40+
41+
const subFromIdToken = decodeJwt(idToken)?.sub;
42+
if (sub !== subFromIdToken) {
43+
throw new Error(
44+
`There was a problem verifying your identity with ${providerName} due to a mismatching 'sub' claim. Please contact your system administrator for assistance.`,
45+
);
46+
}
47+
48+
return ctx.signInWithCatalogUser({
49+
annotations: { [userIdKey]: sub },
50+
});
51+
};
52+
},
53+
});
54+
55+
/**
56+
* Additional sign-in resolvers for the Oidc auth provider.
57+
*
58+
* @public
59+
*/
60+
export namespace rhdhSignInResolvers {
61+
/**
62+
* An OIDC resolver that looks up the user using their Keycloak user ID.
63+
*/
64+
export const oidcSubClaimMatchingKeycloakUserId = createOidcSubClaimResolver(
65+
KEYCLOAK_ID_ANNOTATION,
66+
'Keycloak',
67+
);
68+
69+
/**
70+
* An OIDC resolver that looks up the user using their Ping Identity user ID.
71+
*/
72+
export const oidcSubClaimMatchingPingIdentityUserId =
73+
createOidcSubClaimResolver(PING_IDENTITY_ID_ANNOTATION, 'Ping Identity');
74+
}

yarn.lock

+1
Original file line numberDiff line numberDiff line change
@@ -22538,6 +22538,7 @@ __metadata:
2253822538
"@types/global-agent": 2.1.3
2253922539
app: "*"
2254022540
global-agent: 3.0.0
22541+
jose: ^5.9.6
2254122542
prettier: 3.4.2
2254222543
undici: 6.21.1
2254322544
winston: 3.14.2

0 commit comments

Comments
 (0)