in src/OrchardCore.Modules/OrchardCore.OpenId/Controllers/UserInfoController.cs [22:166]
public UserInfoController(IStringLocalizer<UserInfoController> localizer)
=> S = localizer;
// GET/POST: /connect/userinfo
[AcceptVerbs("GET", "POST")]
[IgnoreAntiforgeryToken]
[Produces("application/json")]
public async Task<IActionResult> Me()
{
// Warning: this action is decorated with IgnoreAntiforgeryTokenAttribute to override
// the global antiforgery token validation policy applied by the MVC modules stack,
// which is required for this stateless OpenID userinfo endpoint to work correctly.
// To prevent effective CSRF/session fixation attacks, this action MUST NOT return
// an authentication cookie or try to establish an ASP.NET Core user session.
var request = HttpContext.GetOpenIddictServerRequest();
if (request == null)
{
return NotFound();
}
// Note: this controller doesn't use [Authorize] to prevent MVC from throwing
// an exception if the OpenIddict server handler was not registered (e.g because the
// OpenID server feature was not enabled or because the configuration was invalid).
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme))?.Principal;
if (principal == null)
{
return Challenge(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
// Ensure the access token represents a user and not an application.
var type = principal.FindFirst(OpenIdConstants.Claims.EntityType)?.Value;
if (!string.Equals(type, OpenIdConstants.EntityTypes.User))
{
return Forbid(new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidRequest,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
S["The userinfo endpoint can only be used with access tokens representing users."]
}), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
var claims = new Dictionary<string, object>();
if (principal.HasScope(Scopes.Profile))
{
var preferredUsername = principal.FindFirst(Claims.PreferredUsername)?.Value;
if (!string.IsNullOrEmpty(preferredUsername))
{
claims[Claims.PreferredUsername] = preferredUsername;
}
var name = principal.FindFirst(Claims.Name)?.Value ?? principal.FindFirst(ClaimTypes.Name)?.Value;
if (!string.IsNullOrEmpty(name))
{
claims[Claims.Name] = name;
}
var familyName = principal.FindFirst(Claims.FamilyName)?.Value ?? principal.FindFirst(ClaimTypes.Surname)?.Value;
if (!string.IsNullOrEmpty(familyName))
{
claims[Claims.FamilyName] = familyName;
}
var givenName = principal.FindFirst(Claims.GivenName)?.Value ?? principal.FindFirst(ClaimTypes.GivenName)?.Value;
if (!string.IsNullOrEmpty(givenName))
{
claims[Claims.GivenName] = givenName;
}
var middleName = principal.FindFirst(Claims.MiddleName)?.Value;
if (!string.IsNullOrEmpty(middleName))
{
claims[Claims.MiddleName] = middleName;
}
var picture = principal.FindFirst(Claims.Picture)?.Value;
if (!string.IsNullOrEmpty(picture))
{
claims[Claims.Picture] = picture;
}
var updatedAtClaimValue = principal.FindFirst(Claims.UpdatedAt)?.Value;
if (!string.IsNullOrEmpty(updatedAtClaimValue))
{
claims[Claims.UpdatedAt] = long.Parse(updatedAtClaimValue, CultureInfo.InvariantCulture);
}
}
// Note: the "sub" claim is a mandatory claim and must be included in the JSON response.
claims[Claims.Subject] = principal.GetUserIdentifier();
if (principal.HasScope(Scopes.Email))
{
var address = principal.FindFirst(Claims.Email)?.Value ?? principal.FindFirst(ClaimTypes.Email)?.Value;
if (!string.IsNullOrEmpty(address))
{
claims[Claims.Email] = address;
var status = principal.FindFirst(Claims.EmailVerified)?.Value;
if (!string.IsNullOrEmpty(status))
{
claims[Claims.EmailVerified] = bool.Parse(status);
}
}
}
if (principal.HasScope(Scopes.Phone))
{
var phone = principal.FindFirst(Claims.PhoneNumber)?.Value ??
principal.FindFirst(ClaimTypes.MobilePhone)?.Value ??
principal.FindFirst(ClaimTypes.HomePhone)?.Value ??
principal.FindFirst(ClaimTypes.OtherPhone)?.Value;
if (!string.IsNullOrEmpty(phone))
{
claims[Claims.PhoneNumber] = phone;
var status = principal.FindFirst(Claims.PhoneNumberVerified)?.Value;
if (!string.IsNullOrEmpty(status))
{
claims[Claims.PhoneNumberVerified] = bool.Parse(status);
}
}
}
if (principal.HasScope(Scopes.Roles))
{
var roles = principal.FindAll(Claims.Role)
.Concat(principal.FindAll(ClaimTypes.Role))
.Select(claim => claim.Value)
.ToArray();
if (roles.Length != 0)
{
claims["roles"] = roles;
}
}
// Note: the complete list of standard claims supported by the OpenID Connect specification
// can be found here: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
return Ok(claims);
}