Skip to content

feat(keycloak): add keycloak-focused secure sign-in resolvers#9117

Open
JessicaJHee wants to merge 1 commit into
backstage:mainfrom
JessicaJHee:add-keycloak-resolvers
Open

feat(keycloak): add keycloak-focused secure sign-in resolvers#9117
JessicaJHee wants to merge 1 commit into
backstage:mainfrom
JessicaJHee:add-keycloak-resolvers

Conversation

@JessicaJHee
Copy link
Copy Markdown
Contributor

@JessicaJHee JessicaJHee commented May 13, 2026

Hey, I just made a Pull Request!

Added two secure Keycloak-focused sign-in resolvers that resolve users using immutable identifiers (like the OIDC sub claim and LDAP UUID). They provide a more secure way to resolver users compared to matching on email or username which users can change to help reduce risk of impersonation.

  • oidcSubClaimMatchingKeycloakUserId: Resolves users via the keycloak.org/id annotation from the Keycloak catalog provider using OIDC sub from userinfo, verified to match with the ID token sub claim.
  • ldapUuidMatchingAnnotation: Resolves users via backstage.io/ldap-uuid using a configurable claim (default ldap_uuid), with UUID validation and userinfo/ID-token check

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)
  • All your commits have a Signed-off-by line in the message. (more info)

Signed-off-by: Jessica He <jhe@redhat.com>
Copilot AI review requested due to automatic review settings May 13, 2026 14:13
@JessicaJHee JessicaJHee requested a review from Parsifal-M May 13, 2026 14:14
@backstage-goalie
Copy link
Copy Markdown
Contributor

Changed Packages

Package Name Package Path Changeset Bump Current Version
@backstage-community/plugin-auth-backend-module-keycloak-provider workspaces/keycloak/plugins/auth-backend-module-keycloak minor v0.0.0

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@JessicaJHee
Copy link
Copy Markdown
Contributor Author

@kz-masa I'm proposing to contribute these 2 additional auth providers in Keycloak and thought that it may interest you, please take a look :)

Copy link
Copy Markdown
Contributor

@kz-masa kz-masa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @JessicaJHee, thanks for reviewing my previous PR, and thank you for submitting this excellent contribution! Adding secure Keycloak-focused resolvers using OIDC sub claims and LDAP UUIDs is a great enhancement for security.

Overall, the implementation looks solid. I've left a few inline comments and suggestions. Specifically, I found a type mismatch where entityRef is passed in a format that signInWithCatalogUser does not accept, which needs to be fixed. I've also suggested adding a test case for the malformed UUID check to ensure our coverage remains robust.

Could you take a look at the suggested changes? Let me know if you have any questions.

{
dangerousEntityRefFallback:
options?.dangerouslyAllowSignInWithoutUserInCatalog
? { entityRef: uuidFromUserinfo.data }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dengerousEntityRefFallback param accepts entityRef as either a full entity ref string (e.g. "user:default/alice") or an object { kind?, namespace?, name }.
The sibling resolvers referredUsernameMatchingUserEntityName and oidcSubClaimMatchingKeycloakUserId handle this correctly with the object form — would be great to stay consistent

Suggested change
? { entityRef: uuidFromUserinfo.data }
? { entityRef: { name: uuidFromUserinfo.data } }

expect(ctx.signInWithCatalogUser).toHaveBeenCalledWith(
{ annotations: { 'backstage.io/ldap-uuid': ldapUuid } },
{
dangerousEntityRefFallback: { entityRef: ldapUuid },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure to update the corresponding test expectation as well.

Suggested change
dangerousEntityRefFallback: { entityRef: ldapUuid },
dangerousEntityRefFallback: { entityRef: { name: ldapUuid } },

await expect(resolver(info, ctx)).rejects.toThrow(/ldap_uuid/);
expect(ctx.signInWithCatalogUser).not.toHaveBeenCalled();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In resolvers.ts on lines 187-192, there is a check for a malformed UUID (z.string().uuid().safeParse), but it seems untested. Adding this test would be awesome for keeping our coverage robust.

Suggested change
it('throws when id token claim is not a valid UUID string', async () => {
const ctx = createMockContext();
const resolver = keycloakSignInResolvers.ldapUuidMatchingAnnotation();
const validUuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
const idToken = await signTestIdToken({
sub: 'user-sub',
ldap_uuid: 'not-a-uuid'
});
const info = createInfo({ ldap_uuid: validUuid }, {}, idToken);
await expect(resolver(info, ctx)).rejects.toThrow(/valid UUID string/);
expect(ctx.signInWithCatalogUser).not.toHaveBeenCalled();
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants