public async Task <ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { //transform can run more than once. //the service is scoped, so setting and testing the Principal property allows short-circuiting if (ScopePrincipal != null) { return(ScopePrincipal); } ScopePrincipal = principal; //if there is no oid, then principal didn't come from AzureAD / OpenIdConnect if (principal.GetObjectId() == null) { return(principal); } var claimsIdentity = principal.Identities.First(); //mark principal with claim for this transform claimsIdentity.AddClaim(new Claim("transform", GetType()?.FullName ?? "AzureGroupsClaimsTransform")); var claimsCacheResult = await Cache.GetGroupClaimsAsync(principal); var groupNames = claimsCacheResult.Value; if (!claimsCacheResult.Success) { var accessToken = await TokenAcquisition.GetAccessTokenForAppAsync(Options.GraphEndpoint, authenticationScheme : Options.Scheme); try { groupNames = (await GraphService.GroupsAsync(principal, accessToken))?.Select(x => x.DisplayName); } catch (Exception e) { var requestId = Activity.Current?.Id ?? "<null>"; Logger.LogCritical(e, "AzureGroupsClaimsTransform exception from RequestId: {requestId}.", requestId); claimsIdentity.AddClaim(new Claim("transform-error", e.Message)); claimsIdentity.AddClaim(new Claim("transform-error-request-id", requestId)); return(principal); } if (groupNames != null) { await Cache.SetGroupClaimsAsync(principal, groupNames, Options.DistributedCacheEntryOptions); } } foreach (var group in groupNames !) { claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, group, ClaimValueTypes.String, "AzureAD")); } return(principal); }