通过 OpenID Connect 从 Azure AD 获取用户的电子邮件地址
Get the user's email address from Azure AD via OpenID Connect
我正在尝试使用他们的 Office 365 帐户对访问我网站的用户进行身份验证,因此我一直在遵循有关使用 OWIN OpenID Connect 中间件添加身份验证的指南,并成功设法对他们的个人资料进行身份验证和检索。
我现在正在尝试获取用户的电子邮件地址(这样我就可以用他们的联系方式填充他们的系统帐户),但我似乎无法收回电子邮件声明。我已尝试使用范围 openid profile email
发出请求,但声明集不包含任何邮件信息。
有没有办法通过 OpenID Connect 端点从 Azure AD 获取用户的电子邮件?
您是否可以通过 &resource=https://graph.windows.net in the sign-in request to the authorization endpoint, then query the Azure AD Graph API for the authenticated organizational user's Office 365 email address? For example, GET https://graph.windows.net/me/mail?api-version=1.5
有关其他参考,请参阅 AzureADSamples GitHub 上的 WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet 代码示例。
在找到解决方案之前,我为同样的问题苦苦挣扎了几天。回答您的问题:是的,只要您:
,您就应该能够在您的索赔中取回电子邮件地址
- 在您的请求中包含
profile
或 email
范围,并且
- 在 Azure 门户 Active Directory 部分配置您的应用程序以在 委派权限.[=75 下包含登录并读取用户配置文件 =]
请注意,电子邮件地址可能不会在 email
声明中返回:在我的情况下(一旦我开始工作)它会在 name
声明中返回。
但是,根本无法取回电子邮件地址可能是由以下问题之一引起的:
没有与 Azure AD 帐户关联的电子邮件地址
根据本 Scopes, permissions, and consent in the Azure Active Directory v2.0 endpoint 指南,即使您包含 email
范围,您也可能无法取回电子邮件地址:
The email
claim is included in a token only if an email address is associated with the user account, which is not always the case. If it uses the email
scope, your app should be prepared to handle a case in which the email
claim does not exist in the token.
如果您收到其他与个人资料相关的声明(例如 given_name
和 family_name
),这可能就是问题所在。
被中间件丢弃的声明
这就是我的原因。我没有收到 任何 个人资料相关的声明(名字、姓氏、用户名、电子邮件等)。
在我的例子中,身份处理堆栈如下所示:
- IdentityServer3
- IdentityServer3.AspNetIdentity
- 基于 couchbase-aspnet-identity
的自定义 Couchbase 存储提供程序
问题出在 IdentityServer3.AspNetIdentity AspNetIdentityUserService
class: InstantiateNewUserFromExternalProviderAsync()
method looks like this:
protected virtual Task<TUser> InstantiateNewUserFromExternalProviderAsync(
string provider,
string providerId,
IEnumerable<Claim> claims)
{
var user = new TUser() { UserName = Guid.NewGuid().ToString("N") };
return Task.FromResult(user);
}
注意它传入一个声明集合然后忽略它。我的解决方案是创建一个由此派生的 class 并将该方法重写为如下内容:
protected override Task<TUser> InstantiateNewUserFromExternalProviderAsync(
string provider,
string providerId,
IEnumerable<Claim> claims)
{
var user = new TUser
{
UserName = Guid.NewGuid().ToString("N"),
Claims = claims
};
return Task.FromResult(user);
}
我不确切知道您使用的是什么中间件组件,但很容易看到从您的外部提供商返回的原始声明;这至少会告诉您它们恢复正常,并且问题出在您的中间件中。只需将 Notifications
属性 添加到您的 OpenIdConnectAuthenticationOptions
对象,如下所示:
// Configure Azure AD as a provider
var azureAdOptions = new OpenIdConnectAuthenticationOptions
{
AuthenticationType = Constants.Azure.AuthenticationType,
Caption = Resources.AzureSignInCaption,
Scope = Constants.Azure.Scopes,
ClientId = Config.Azure.ClientId,
Authority = Constants.Azure.AuthenticationRootUri,
PostLogoutRedirectUri = Config.Identity.RedirectUri,
RedirectUri = Config.Azure.PostSignInRedirectUri,
AuthenticationMode = AuthenticationMode.Passive,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = context =>
{
// Log all the claims returned by Azure AD
var claims = context.AuthenticationTicket.Identity.Claims;
foreach (var claim in claims)
{
Log.Debug("{0} = {1}", claim.Type, claim.Value);
}
return null;
}
},
SignInAsAuthenticationType = signInAsType // this MUST come after TokenValidationParameters
};
app.UseOpenIdConnectAuthentication(azureAdOptions);
另见
- This article by Scott Brady 包含一个关于 Claims Transformation 的部分,如果以上都没有解决它可能会有用。
- This discussion on the IdentityServer3 GitHub account was a huge help to me, especially this response.
几天来我一直在为同一个问题苦苦挣扎...我从拥有个人 Microsoft 帐户的用户那里获得了电子邮件地址,但没有获得拥有公司 Microsoft 帐户的用户的电子邮件地址。
对于个人帐户,电子邮件地址会像人们期望的那样在 email
字段中返回。
对于公司帐户,电子邮件地址在 preferred_username
字段中返回。
祝我好运,没有我还没有发现的另一个 Microsoft 变体...
2019 年的更新答案:email
声明是一项可选声明,可能未包含在请求中 (Source)
For managed users (those inside the tenant), it must be requested through this optional claim or, on v2.0 only, with the OpenID scope.
您必须更新 Azure 门户中的清单文件以包含可选声明,如下所示:
"optionalClaims": {
"idToken": [
{
"name": "email",
"source": null,
"essential": false,
"additionalProperties": []
}
],
}
此回答的部分灵感来自 this blog post。
在同样的情况下,我得到了一个非常简单的代码,return 所有用户都声称在登录后可以访问(包括电子邮件)
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
namespace Controllers
{
public class BaseController : Controller
{
protected string GetCurrentUserIDFromClaims()
{
return User.FindFirstValue(ClaimTypes.NameIdentifier);
}
protected List<string> AllClaimsFromAzure()
{
ClaimsIdentity claimsIdentity = ((ClaimsIdentity)User.Identity);
return claimsIdentity.Claims.Select(x => x.Value).ToList();
}
protected string GetCurrentEmailFromAzureClaims()
{
return AllClaimsFromAzure()[3];
}
}
}
从用户声明中获取 upn
值:
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
string email = userClaims?.FindFirst(System.Security.Claims.ClaimTypes.Upn)?.Value;
我正在尝试使用他们的 Office 365 帐户对访问我网站的用户进行身份验证,因此我一直在遵循有关使用 OWIN OpenID Connect 中间件添加身份验证的指南,并成功设法对他们的个人资料进行身份验证和检索。
我现在正在尝试获取用户的电子邮件地址(这样我就可以用他们的联系方式填充他们的系统帐户),但我似乎无法收回电子邮件声明。我已尝试使用范围 openid profile email
发出请求,但声明集不包含任何邮件信息。
有没有办法通过 OpenID Connect 端点从 Azure AD 获取用户的电子邮件?
您是否可以通过 &resource=https://graph.windows.net in the sign-in request to the authorization endpoint, then query the Azure AD Graph API for the authenticated organizational user's Office 365 email address? For example, GET https://graph.windows.net/me/mail?api-version=1.5
有关其他参考,请参阅 AzureADSamples GitHub 上的 WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet 代码示例。
在找到解决方案之前,我为同样的问题苦苦挣扎了几天。回答您的问题:是的,只要您:
,您就应该能够在您的索赔中取回电子邮件地址- 在您的请求中包含
profile
或email
范围,并且 - 在 Azure 门户 Active Directory 部分配置您的应用程序以在 委派权限.[=75 下包含登录并读取用户配置文件 =]
请注意,电子邮件地址可能不会在 email
声明中返回:在我的情况下(一旦我开始工作)它会在 name
声明中返回。
但是,根本无法取回电子邮件地址可能是由以下问题之一引起的:
没有与 Azure AD 帐户关联的电子邮件地址
根据本 Scopes, permissions, and consent in the Azure Active Directory v2.0 endpoint 指南,即使您包含 email
范围,您也可能无法取回电子邮件地址:
The
如果您收到其他与个人资料相关的声明(例如 given_name
和 family_name
),这可能就是问题所在。
被中间件丢弃的声明
这就是我的原因。我没有收到 任何 个人资料相关的声明(名字、姓氏、用户名、电子邮件等)。
在我的例子中,身份处理堆栈如下所示:
- IdentityServer3
- IdentityServer3.AspNetIdentity
- 基于 couchbase-aspnet-identity 的自定义 Couchbase 存储提供程序
问题出在 IdentityServer3.AspNetIdentity AspNetIdentityUserService
class: InstantiateNewUserFromExternalProviderAsync()
method looks like this:
protected virtual Task<TUser> InstantiateNewUserFromExternalProviderAsync(
string provider,
string providerId,
IEnumerable<Claim> claims)
{
var user = new TUser() { UserName = Guid.NewGuid().ToString("N") };
return Task.FromResult(user);
}
注意它传入一个声明集合然后忽略它。我的解决方案是创建一个由此派生的 class 并将该方法重写为如下内容:
protected override Task<TUser> InstantiateNewUserFromExternalProviderAsync(
string provider,
string providerId,
IEnumerable<Claim> claims)
{
var user = new TUser
{
UserName = Guid.NewGuid().ToString("N"),
Claims = claims
};
return Task.FromResult(user);
}
我不确切知道您使用的是什么中间件组件,但很容易看到从您的外部提供商返回的原始声明;这至少会告诉您它们恢复正常,并且问题出在您的中间件中。只需将 Notifications
属性 添加到您的 OpenIdConnectAuthenticationOptions
对象,如下所示:
// Configure Azure AD as a provider
var azureAdOptions = new OpenIdConnectAuthenticationOptions
{
AuthenticationType = Constants.Azure.AuthenticationType,
Caption = Resources.AzureSignInCaption,
Scope = Constants.Azure.Scopes,
ClientId = Config.Azure.ClientId,
Authority = Constants.Azure.AuthenticationRootUri,
PostLogoutRedirectUri = Config.Identity.RedirectUri,
RedirectUri = Config.Azure.PostSignInRedirectUri,
AuthenticationMode = AuthenticationMode.Passive,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = context =>
{
// Log all the claims returned by Azure AD
var claims = context.AuthenticationTicket.Identity.Claims;
foreach (var claim in claims)
{
Log.Debug("{0} = {1}", claim.Type, claim.Value);
}
return null;
}
},
SignInAsAuthenticationType = signInAsType // this MUST come after TokenValidationParameters
};
app.UseOpenIdConnectAuthentication(azureAdOptions);
另见
- This article by Scott Brady 包含一个关于 Claims Transformation 的部分,如果以上都没有解决它可能会有用。
- This discussion on the IdentityServer3 GitHub account was a huge help to me, especially this response.
几天来我一直在为同一个问题苦苦挣扎...我从拥有个人 Microsoft 帐户的用户那里获得了电子邮件地址,但没有获得拥有公司 Microsoft 帐户的用户的电子邮件地址。
对于个人帐户,电子邮件地址会像人们期望的那样在 email
字段中返回。
对于公司帐户,电子邮件地址在 preferred_username
字段中返回。
祝我好运,没有我还没有发现的另一个 Microsoft 变体...
2019 年的更新答案:email
声明是一项可选声明,可能未包含在请求中 (Source)
For managed users (those inside the tenant), it must be requested through this optional claim or, on v2.0 only, with the OpenID scope.
您必须更新 Azure 门户中的清单文件以包含可选声明,如下所示:
"optionalClaims": {
"idToken": [
{
"name": "email",
"source": null,
"essential": false,
"additionalProperties": []
}
],
}
此回答的部分灵感来自 this blog post。
在同样的情况下,我得到了一个非常简单的代码,return 所有用户都声称在登录后可以访问(包括电子邮件)
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
namespace Controllers
{
public class BaseController : Controller
{
protected string GetCurrentUserIDFromClaims()
{
return User.FindFirstValue(ClaimTypes.NameIdentifier);
}
protected List<string> AllClaimsFromAzure()
{
ClaimsIdentity claimsIdentity = ((ClaimsIdentity)User.Identity);
return claimsIdentity.Claims.Select(x => x.Value).ToList();
}
protected string GetCurrentEmailFromAzureClaims()
{
return AllClaimsFromAzure()[3];
}
}
}
从用户声明中获取 upn
值:
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
string email = userClaims?.FindFirst(System.Security.Claims.ClaimTypes.Upn)?.Value;