How to pass information from OnAuthenticated event

2019-03-17 00:24发布

问题:

As I've found out here, I can't call

HttpContext.GetOwinContext().Authentication.SignIn(...)

in FacebookAuthenticationProvider OnAuthenticated event. It seems to be a different context.

My users will be from Facebook, which means that I will need to retrieve data such as id, email... This information is accessible on the OnAuthenticated event and I need this information to SignIn.

I need to access this information on my controller...

I tried this in the event:

context.OwinContext.Set("FacebookUser", rawUserObjectFromFacebookAsJson);

But on the controller if I try to retrieve this, it is null.

var fbuser = HttpContext.GetOwinContext()
                 .Get<Newtonsoft.Json.Linq.JObject>("FacebookUser");

So my question is, how can I pass this data to the controller so I can sign in?

回答1:

I found a way to do this by looking at this answer and figuring out what I could do.

Using Claims I can add all values retrieved from Facebook and throw into identity claims.

OnAuthenticated = (context) =>
{
    const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";

    var rawUserObjectFromFacebookAsJson = context.User;

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

Then on my controller I can get that identity by using this

ClaimsIdentity identity = await HttpContext.GetOwinContext().Authentication
    .GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

Then I will have on my action

[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
    ClaimsIdentity identity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

    var user = new IdentityUser()
    {
        Id = identity.GetUserId(),
        UserName = identity.Name,
    };

    await LoginAsync(user, identity);

    if (!identity.IsAuthenticated)
    {
        return RedirectToAction("Login");
    }

    return RedirectToAction("Index", "Home");
}

And my LoginAsync method

private async Task LoginAsync(IdentityUser user, ClaimsIdentity identity)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);

    // I can't just use the identity I got on Facebook
    // I need to create this one, or else it will not signin properly
    // The authentication type has to be ApplicationCookie and the property
    // is readonly, so...
    var userIdentity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

    // now we have to transfer the claims, adding a check to avoid duplicates
    foreach (var claim in identity.Claims)
    {
        if (!userIdentity.HasClaim(c => c.Type == claim.Type))
            userIdentity.AddClaim(claim);
    }

    // then it will signin successfully
    AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, userIdentity);
}

Then I can access

HttpContext.GetOwinContext().Authentication.User.Claims

at any time and retrieve what I need.