feat(oidc-provider): add client to getAdditionalUserInfoClaim callback (#3790)

* feat: add oidc Client to getAdditionalUserInfoClaim

* address cubic comments

* run format
This commit is contained in:
Grant G
2025-08-05 21:17:42 -07:00
committed by Bereket Engida
parent 84b5db26f8
commit b2ac809e5e
6 changed files with 35 additions and 9 deletions

View File

@@ -246,7 +246,7 @@ The UserInfo endpoint returns different claims based on the scopes that were gra
- With `profile` scope: Returns name, picture, given_name, family_name
- With `email` scope: Returns email and email_verified
The `getAdditionalUserInfoClaim` function receives the user object and the requested scopes array, allowing you to conditionally include claims based on the scopes granted during authorization. These additional claims will be included in both the UserInfo endpoint response and the ID token.
The `getAdditionalUserInfoClaim` function receives the user object, requested scopes array, and the client, allowing you to conditionally include claims based on the scopes granted during authorization. These additional claims will be included in both the UserInfo endpoint response and the ID token.
### Consent Screen
@@ -561,6 +561,6 @@ Table Name: `oauthConsent`
**trustedClients**: `(Client & { skipConsent?: boolean })[]` - Array of trusted clients that are configured directly in the provider options. These clients bypass database lookups and can optionally skip consent screens.
**getAdditionalUserInfoClaim**: `(user: User, scopes: string[]) => Record<string, any>` - Function to get additional user info claims.
**getAdditionalUserInfoClaim**: `(user: User, scopes: string[], client: Client) => Record<string, any>` - Function to get additional user info claims.
**useJWTPlugin**: `boolean` - When `true`, ID tokens are signed using the JWT plugin's asymmetric keys. When `false` (default), ID tokens are signed with HMAC-SHA256 using the application secret.

View File

@@ -570,7 +570,11 @@ export const mcp = (options: MCPOptions) => {
};
const additionalUserClaims = opts.getAdditionalUserInfoClaim
? opts.getAdditionalUserInfoClaim(user, requestedScopes)
? await opts.getAdditionalUserInfoClaim(
user,
requestedScopes,
client,
)
: {};
const idToken = await new SignJWT({

View File

@@ -31,7 +31,7 @@ describe("mcp", async () => {
loginPage: "/login",
requirePKCE: true,
getAdditionalUserInfoClaim(user, scopes) {
getAdditionalUserInfoClaim(user, scopes, client) {
return {
custom: "custom value",
userId: user.id,

View File

@@ -754,7 +754,11 @@ export const oidcProvider = (options: OIDCOptions) => {
};
const additionalUserClaims = options.getAdditionalUserInfoClaim
? await options.getAdditionalUserInfoClaim(user, requestedScopes)
? await options.getAdditionalUserInfoClaim(
user,
requestedScopes,
client,
)
: {};
const payload = {
@@ -958,6 +962,18 @@ export const oidcProvider = (options: OIDCOptions) => {
});
}
const client = await getClient(
accessToken.clientId,
ctx.context.adapter,
trustedClients,
);
if (!client) {
throw new APIError("UNAUTHORIZED", {
error_description: "client not found",
error: "invalid_token",
});
}
const user = await ctx.context.internalAdapter.findUserById(
accessToken.userId,
);
@@ -986,7 +1002,11 @@ export const oidcProvider = (options: OIDCOptions) => {
: undefined,
};
const userClaims = options.getAdditionalUserInfoClaim
? await options.getAdditionalUserInfoClaim(user, requestedScopes)
? await options.getAdditionalUserInfoClaim(
user,
requestedScopes,
client,
)
: baseUserClaims;
return ctx.json({
...baseUserClaims,

View File

@@ -32,7 +32,7 @@ describe("oidc", async () => {
loginPage: "/login",
consentPage: "/oauth2/authorize",
requirePKCE: true,
getAdditionalUserInfoClaim(user, scopes) {
getAdditionalUserInfoClaim(user, scopes, client) {
return {
custom: "custom value",
userId: user.id,
@@ -376,7 +376,7 @@ describe("oidc storage", async () => {
loginPage: "/login",
consentPage: "/oauth2/authorize",
requirePKCE: true,
getAdditionalUserInfoClaim(user, scopes) {
getAdditionalUserInfoClaim(user, scopes, client) {
return {
custom: "custom value",
userId: user.id,
@@ -535,7 +535,7 @@ describe("oidc-jwt", async () => {
loginPage: "/login",
consentPage: "/oauth2/authorize",
requirePKCE: true,
getAdditionalUserInfoClaim(user, scopes) {
getAdditionalUserInfoClaim(user, scopes, client) {
return {
custom: "custom value",
userId: user.id,

View File

@@ -115,11 +115,13 @@ export interface OIDCOptions {
*
* @param user - The user object.
* @param scopes - The scopes that the client requested.
* @param client - The client object.
* @returns The user info claim.
*/
getAdditionalUserInfoClaim?: (
user: User & Record<string, any>,
scopes: string[],
client: Client,
) => Record<string, any> | Promise<Record<string, any>>;
/**
* Trusted clients that are configured directly in the provider options.