Repository: directus/directus
File: api/src/auth/drivers/openid.ts (TypeScript)

Code before:
			userInfo = tokenSet.claims();

			if (client.issuer.metadata['userinfo_endpoint']) {
				userInfo = {
					...userInfo,
					...(await client.userinfo(tokenSet.access_token!)),
				};
			}
		} catch (e) {
			throw handleError(e);
		}

		let role = this.config['defaultRoleId'];
		const groupClaimName: string = this.config['groupClaimName'] ?? 'groups';

		if (userInfo[groupClaimName] === undefined) {
			logger.error(
				'[OpenID] The groups claim name configured could not be found in the data received from the OIDC provider. Please check if your groups claim name is set correctly.',
			);

			throw new InvalidProviderConfigError({ provider: this.config['provider'] });
		}

		// Overwrite default role if user is member of a group specified in roleMap
		for (const key in this.roleMap) {
			if ((userInfo[groupClaimName] as Array<string>).includes(key)) {
				role = this.roleMap[key];
				break;
			}
		}

		// Flatten response to support dot indexes
		userInfo = flatten(userInfo) as Record<string, unknown>;

		const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail, syncUserInfo } = this.config;

		const email = userInfo['email'] ? String(userInfo['email']) : undefined;
		// Fallback to email if explicit identifier not found
		const identifier = userInfo[identifierKey ?? 'sub'] ? String(userInfo[identifierKey ?? 'sub']) : email;

		if (!identifier) {
			logger.warn(`[OpenID] Failed to find user identifier for provider "${provider}"`);
			throw new InvalidCredentialsError();
		}

		const userPayload = {
			provider,
			first_name: userInfo['given_name'],
			last_name: userInfo['family_name'],
			email: email,
			external_identifier: identifier,

Review comment (bug):
```suggestion
                        const groups = userInfo[groupClaimName];
			if (Array.isArray(groups) && groups.includes(key)) {
```

Might also be good to add some logging if `groups` is not a valid array to help users debug their claims.

Code after:
			userInfo = tokenSet.claims();

			if (client.issuer.metadata['userinfo_endpoint']) {
				userInfo = {
					...userInfo,
					...(await client.userinfo(tokenSet.access_token!)),
				};
			}
		} catch (e) {
			throw handleError(e);
		}

		let role = this.config['defaultRoleId'];
		const groupClaimName: string = this.config['groupClaimName'] ?? 'groups';
		const groups = userInfo[groupClaimName];

		if (Array.isArray(groups)) {
			for (const key in this.roleMap) {
				if (groups.includes(key)) {
					// Overwrite default role if user is member of a group specified in roleMap
					role = this.roleMap[key];
					break;
				}
			}
		} else {
			logger.debug(`[OpenID] Configured group claim with name "${groupClaimName}" does not exist or is empty.`);
		}

		// Flatten response to support dot indexes
		userInfo = flatten(userInfo) as Record<string, unknown>;

		const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail, syncUserInfo } = this.config;

		const email = userInfo['email'] ? String(userInfo['email']) : undefined;
		// Fallback to email if explicit identifier not found
		const identifier = userInfo[identifierKey ?? 'sub'] ? String(userInfo[identifierKey ?? 'sub']) : email;

		if (!identifier) {
			logger.warn(`[OpenID] Failed to find user identifier for provider "${provider}"`);
			throw new InvalidCredentialsError();
		}

		const userPayload = {
			provider,
			first_name: userInfo['given_name'],
			last_name: userInfo['family_name'],
			email: email,
			external_identifier: identifier,
			role: role,
			auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
		};