@@ -1134,6 +1134,143 @@ describe("oauth token - client_credentials", async () => {
11341134 } ) ;
11351135} ) ;
11361136
1137+ describe ( "oauth token - customIdTokenClaims precedence" , async ( ) => {
1138+ const authServerBaseUrl = "http://localhost:3000" ;
1139+ const rpBaseUrl = "http://localhost:5000" ;
1140+ const { auth, signInWithTestUser, customFetchImpl, testUser } =
1141+ await getTestInstance ( {
1142+ baseURL : authServerBaseUrl ,
1143+ plugins : [
1144+ jwt ( {
1145+ jwt : {
1146+ issuer : authServerBaseUrl ,
1147+ } ,
1148+ } ) ,
1149+ oauthProvider ( {
1150+ loginPage : "/login" ,
1151+ consentPage : "/consent" ,
1152+ silenceWarnings : {
1153+ oauthAuthServerConfig : true ,
1154+ openidConfig : true ,
1155+ } ,
1156+ customIdTokenClaims : ( ) => ( {
1157+ given_name : "CustomFirst" ,
1158+ family_name : "CustomLast" ,
1159+ custom_field : "custom_value" ,
1160+ } ) ,
1161+ } ) ,
1162+ ] ,
1163+ } ) ;
1164+
1165+ const { headers } = await signInWithTestUser ( ) ;
1166+ const client = createAuthClient ( {
1167+ plugins : [ oauthProviderClient ( ) , jwtClient ( ) ] ,
1168+ baseURL : authServerBaseUrl ,
1169+ fetchOptions : {
1170+ customFetchImpl,
1171+ headers,
1172+ } ,
1173+ } ) ;
1174+
1175+ let oauthClient : OAuthClient | null ;
1176+ const providerId = "test" ;
1177+ const redirectUri = `${ rpBaseUrl } /api/auth/oauth2/callback/${ providerId } ` ;
1178+ const state = "123" ;
1179+ let jwks : ReturnType < typeof createLocalJWKSet > ;
1180+
1181+ beforeAll ( async ( ) => {
1182+ const response = await auth . api . adminCreateOAuthClient ( {
1183+ headers,
1184+ body : {
1185+ redirect_uris : [ redirectUri ] ,
1186+ skip_consent : true ,
1187+ } ,
1188+ } ) ;
1189+ expect ( response ?. client_id ) . toBeDefined ( ) ;
1190+ expect ( response ?. client_secret ) . toBeDefined ( ) ;
1191+ expect ( response ?. redirect_uris ) . toBeDefined ( ) ;
1192+ oauthClient = response ;
1193+
1194+ const jwksResult = await client . jwks ( ) ;
1195+ if ( ! jwksResult . data ) {
1196+ throw new Error ( "Unable to fetch jwks" ) ;
1197+ }
1198+ jwks = createLocalJWKSet ( jwksResult . data ) ;
1199+ } ) ;
1200+
1201+ it ( "custom claims should override standard profile claims in id_token" , async ( {
1202+ expect,
1203+ } ) => {
1204+ if ( ! oauthClient ?. client_id || ! oauthClient ?. client_secret ) {
1205+ throw Error ( "beforeAll not run properly" ) ;
1206+ }
1207+
1208+ const scopes = [ "openid" , "profile" ] ;
1209+ const codeVerifier = generateRandomString ( 32 ) ;
1210+ const url = await createAuthorizationURL ( {
1211+ id : providerId ,
1212+ options : {
1213+ clientId : oauthClient . client_id ,
1214+ clientSecret : oauthClient . client_secret ,
1215+ redirectURI : redirectUri ,
1216+ } ,
1217+ redirectURI : "" ,
1218+ authorizationEndpoint : `${ authServerBaseUrl } /api/auth/oauth2/authorize` ,
1219+ state,
1220+ scopes,
1221+ codeVerifier,
1222+ } ) ;
1223+
1224+ let callbackRedirectUrl = "" ;
1225+ await client . $fetch ( url . toString ( ) , {
1226+ onError ( context ) {
1227+ callbackRedirectUrl = context . response . headers . get ( "Location" ) || "" ;
1228+ } ,
1229+ } ) ;
1230+ expect ( callbackRedirectUrl ) . not . toBe ( "" ) ;
1231+ expect ( callbackRedirectUrl ) . toContain ( redirectUri ) ;
1232+
1233+ const callbackUrl = new URL ( callbackRedirectUrl ) ;
1234+ const code = callbackUrl . searchParams . get ( "code" ) ;
1235+ const returnedState = callbackUrl . searchParams . get ( "state" ) ;
1236+
1237+ expect ( code ) . toBeTruthy ( ) ;
1238+ expect ( returnedState ) . toBe ( state ) ;
1239+
1240+ const { body, headers : reqHeaders } = createAuthorizationCodeRequest ( {
1241+ code : code ! ,
1242+ codeVerifier,
1243+ redirectURI : redirectUri ,
1244+ options : {
1245+ clientId : oauthClient . client_id ,
1246+ clientSecret : oauthClient . client_secret ,
1247+ redirectURI : redirectUri ,
1248+ } ,
1249+ } ) ;
1250+
1251+ const tokens = await client . $fetch < {
1252+ id_token ?: string ;
1253+ [ key : string ] : unknown ;
1254+ } > ( "/oauth2/token" , {
1255+ method : "POST" ,
1256+ body,
1257+ headers : reqHeaders ,
1258+ } ) ;
1259+
1260+ expect ( tokens . data ?. id_token ) . toBeDefined ( ) ;
1261+ const idToken = await jwtVerify ( tokens . data ?. id_token ! , jwks ) ;
1262+
1263+ // Custom claims must override the auto-derived profile claims
1264+ expect ( idToken . payload . given_name ) . toBe ( "CustomFirst" ) ;
1265+ expect ( idToken . payload . family_name ) . toBe ( "CustomLast" ) ;
1266+ expect ( idToken . payload . custom_field ) . toBe ( "custom_value" ) ;
1267+
1268+ // Standard name should still come from the user record (not overridden)
1269+ expect ( idToken . payload . name ) . toBe ( testUser . name ) ;
1270+ expect ( idToken . payload . sub ) . toBeDefined ( ) ;
1271+ } ) ;
1272+ } ) ;
1273+
11371274describe ( "oauth token - config" , async ( ) => {
11381275 const authServerBaseUrl = "http://localhost:3000" ;
11391276 const rpBaseUrl = "http://localhost:5000" ;
0 commit comments