通过OpenID Connect从Azure AD获取用户的电子邮件地址

时间:2015-06-22 15:16:50

标签: c# owin azure-active-directory openid-connect

我尝试使用他们的Office 365帐户对我的网站的用户进行身份验证,因此我一直遵循使用OWIN OpenID Connect中间件添加身份验证的指导,并成功管理以验证和检索他们的个人资料。

我现在正在尝试获取用户的电子邮件地址(因此我可以使用他们的联系人详细信息填充他们的系统帐户),但我似乎无法收到电子邮件索赔。我尝试使用范围openid profile email发出请求,但声明集不包含任何邮件信息。

有没有办法通过OpenID Connect端点从Azure AD获取用户的电子邮件?

5 个答案:

答案 0 :(得分:11)

在达成解决方案之前,我在几天内遇到了同样的问题。在回答您的问题时:是的,您应该能够在您的索赔中找到电子邮件地址:

  1. 在您的请求中加入profileemail范围,
  2. 在Azure门户Active Directory部分配置您的应用程序,以在委托权限下包含登录并阅读用户个人资料
  3. 请注意,email声明中可能不会返回电子邮件地址:在我的情况下(一旦我开始工作),它将返回name声明。

    但是,没有收到所有的电子邮件地址可能是由以下问题之一引起的:

    没有与Azure AD帐户关联的电子邮件地址

    根据Scopes, permissions, and consent in the Azure Active Directory v2.0 endpoint的指南,即使您包含email范围,您也可能无法收到电子邮件地址:

      

    仅当电子邮件地址与用户帐户相关联时,email声明才会包含在令牌中,但情况并非总是如此。如果它使用email范围,您的应用应准备好处理令牌中不存在email声明的情况。

    如果您收到其他与个人资料相关的声明(例如given_namefamily_name),则可能是问题所在。

    中间件丢弃的声明

    这是我的原因。我没有收到任何与个人资料相关的声明(名字,姓氏,用户名,电子邮件等)。

    就我而言,身份处理堆栈如下所示:

    问题在于IdentityServer3.AspNetIdentity AspNetIdentityUserService类: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);
    }
    

    请注意,它会传入声明集合,然后忽略它。我的解决方案是创建一个派生自此的类,并将方法重写为:

    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);
    

    另见

答案 1 :(得分:3)

是否可以选择将登录请求中的&amp; resource = https://graph.windows.net传递给授权端点,然后在Azure AD Graph API中查询经过身份验证的组织用户的Office 365电子邮件地址?例如,GET https://graph.windows.net/me/mail?api-version=1.5

有关其他参考,请参阅AzureADSamples GitHub上的WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet代码示例。

答案 2 :(得分:2)

我几天来一直在努力解决同样的问题......我从个人微软账户的用户那里获得了电子邮件地址,但没有那些拥有微软公司账号的用户。

对于个人帐户,电子邮件地址会在email字段中返回,就像人们期望的那样。

对于公司帐户,电子邮件地址将在preferred_username字段中返回。

保持我的手指交叉,我还没有发现另一个微软的变种...

答案 3 :(得分:1)

在相同的情况下,我最终得到了一个非常简单的代码,该代码返回了登录后所有可访问的用户声明(包括电子邮件)

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];
        }
    }
}

答案 4 :(得分:0)

2019年更新的答案:email声明是可选的声明,可能不包含在请求中(Source

  

对于受管用户(租户内部的用户),必须通过此可选声明或仅在v2.0上使用OpenID范围来请求它。

您必须在Azure门户中更新清单文件以包括可选声明,如下所示:

"optionalClaims": {
    "idToken": [
        {
            "name": "email",
            "source": null,
            "essential": false,
            "additionalProperties": []
        }
    ],
}

此答案部分受this blog post的启发。