Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
302 views
in Technique[技术] by (71.8m points)

asp.net - How do I access Microsoft.Owin.Security.xyz OnAuthenticated context AddClaims values?

I'm trying to retrieve user properties that are returned as the OnAuthenticated context and added as a claims following this example: How to access Facebook private information by using ASP.NET Identity (OWIN)?

I can see that data I am expecting is being returned at login and is being added as a Claim within Starup.Auth.cs. But, when I am within the Account Controller, the only claims that appears within the UserManager or UserStore is issued by LOCAL AUTHORITY. No claims can be found for Facebook (or other external providers). Where do the claims added to context end up? (I'm using VS2013 RTM.)

Full source and live site on Azure linked here: https://github.com/johndpalm/IdentityUserPropertiesSample/tree/VS2013rtm

Here is what I have in Startup.Auth.cs:

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
    AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
    AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
    Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
    {
        OnAuthenticated = (context) =>
            {
                const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
                foreach (var x in context.User)
                {
                    var claimType = string.Format("urn:facebook:{0}", x.Key);
                    string claimValue = x.Value.ToString();
                    if (!context.Identity.HasClaim(claimType, claimValue))
                        context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));

                }
                context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
                return Task.FromResult(0);
            }
    }

};

facebookOptions.Scope.Add("email");

app.UseFacebookAuthentication(facebookOptions);

An alternative way to capture the external login properties would be to add a single claim for the access token and populate it with properties:

const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
    AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
    AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
    Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
    {
        OnAuthenticated = (context) =>
        {
            var claim = new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook");
            foreach (var x in context.User)
            {
                string key = string.Format("urn:facebook:{0}", x.Key);
                string value = x.Value.ToString();
                claim.Properties.Add(key, value);
            }

            context.Identity.AddClaim(claim);

            return Task.FromResult(0);
        }
    }
};

NOTE - This sample does not work: Though it would be nice to pass a single claim with properties. The external cookie seems to note honor the claims properties. The properties are empty when retrieving them later from the identity.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I was able to create a working example, using MVC 5 RTM templates, OWIN, and ASP.NET Identity bits. You can find the complete source and a link to a live working example here: https://github.com/johndpalm/IdentityUserPropertiesSample

Here's what worked for me:

Create a new (insert provider name here) AuthenticationOptions object in Startup.ConfigureAuth (StartupAuth.cs), passing it the client id, client secret, and a new AuthenticationProvider. You will use a lambda expression to pass the OnAuthenticated method some code to add Claims to the identity which contain the values you extract from context.Identity.

StartUp.Auth.cs

// Facebook : Create New App
// https://dev.twitter.com/apps
if (ConfigurationManager.AppSettings.Get("FacebookAppId").Length > 0)
{
    var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
    {
        AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
        AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
        Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
        {
            OnAuthenticated = (context) =>
                {
                    context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
                    foreach (var x in context.User)
                    {
                        var claimType = string.Format("urn:facebook:{0}", x.Key);
                        string claimValue = x.Value.ToString();
                        if (!context.Identity.HasClaim(claimType, claimValue))
                            context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));

                    }
                    return Task.FromResult(0);
                }
        }

    };
    app.UseFacebookAuthentication(facebookOptions);
}

NOTE: The Facebook auth provider works with the code used here. If you use this same code with the Microsoft Account provider (or Foursquare provider I created using the MS account code as a model), it fails to login. If you select just the access_token parameter, it works fine. Seems like some parameters break the login process. (An issue has been opened on katanaproject.codeplex.com if progress on this is of interest to you.) I'll update if I find the cause. I didn't do much with Twitter or Google beyond verifying that I could get the access_token.

var msaccountOptions = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions() 
{
    ClientId = ConfigurationManager.AppSettings.Get("MicrosoftClientId"),
    ClientSecret = ConfigurationManager.AppSettings.Get("MicrosoftClientSecret"),
    Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider()
    {
        OnAuthenticated = (context) =>
            {
                context.Identity.AddClaim(new System.Security.Claims.Claim("urn:microsoftaccount:access_token", context.AccessToken, XmlSchemaString, "Microsoft"));
                return Task.FromResult(0);
            }
    }                   
};

app.UseMicrosoftAccountAuthentication(msaccountOptions);

In AccountController, I extract the ClaimsIdentity from the AuthenticationManager using the external cookie. I then add it to the identity created using the application cookie. I ignored any claims that starts with "...schemas.xmlsoap.org/ws/2005/05/identity/claims" since it seemed to break the login.

AccountController.cs

private async Task SignInAsync(CustomUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

// Extracted the part that has been changed in SignInAsync for clarity.
    await SetExternalProperties(identity);

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

private async Task SetExternalProperties(ClaimsIdentity identity)
{
    // get external claims captured in Startup.ConfigureAuth
    ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

    if (ext != null)
    {
        var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";
        // add external claims to identity
        foreach (var c in ext.Claims)
        {
            if (!c.Type.StartsWith(ignoreClaim))
                if (!identity.HasClaim(c.Type, c.Value))
                    identity.AddClaim(c);
        } 
    }
}

And finally, I want to display whatever values are not from the LOCAL AUTHORITY. I created a partial view _ExternalUserPropertiesListPartial that appears on the /Account/Manage page. I get the claims I previously stored from AuthenticationManager.User.Claims and then pass it to the view.

AccountController.cs

[ChildActionOnly]
public ActionResult ExternalUserPropertiesList()
{
    var extList = GetExternalProperties();
    return (ActionResult)PartialView("_ExternalUserPropertiesListPartial", extList);
}

private List<ExtPropertyViewModel> GetExternalProperties()
{
    var claimlist = from claims in AuthenticationManager.User.Claims
                    where claims.Issuer != "LOCAL AUTHORITY"
                    select new ExtPropertyViewModel
                    {
                        Issuer = claims.Issuer,
                        Type = claims.Type,
                        Value = claims.Value
                    };

    return claimlist.ToList<ExtPropertyViewModel>();
}

And just to be thorough, the view:

_ExternalUserPropertiesListPartial.cshtml

@model IEnumerable<MySample.Models.ExtPropertyViewModel>

@if (Model != null)
{
    <legend>External User Properties</legend>
    <table class="table">
        <tbody>
            @foreach (var claim in Model)
            {
                <tr>
                    <td>@claim.Issuer</td>
                    <td>@claim.Type</td>
                    <td>@claim.Value</td>
                </tr>
            }
        </tbody>
    </table>
}

Again, the working example and complete code is on GitHub: https://github.com/johndpalm/IdentityUserPropertiesSample

And any feedback, corrections, or improvements would be appreciated.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...