1

ASP.NET apps using OWIN permit multiple Identity sources (Facebook, Google, etc.). Most of the provider-specifc information those sources provide is irrelevant to my app, potentially even large, and I don't want it in my cookies all session. My app is primarily WebAPI, but I suspect the question applies equally to MVC and WebForms.

For now, all I need is an integer account ID. Where/when should I reconstruct the identity, after external authentication?

For example, here is one way I could filter claims:

public ReplaceExistingClaims(ClaimsIdentity identity) {
{
    Claim customClaim = GetCustomClaimFromDbForIdentity(identity);
    foreach (Claim claim in ClaimsIdentity.Claims) ClaimsIdentity.RemoveClaim(claim);
    ClaimsIdentity.AddClaim(customClaim);
}

And following are two different places I could inject those claims changes:

var facebookAuthenticationOptions = new FacebookAuthenticationOptions
{
    Provider = new FacebookAuthenticationProvider
    {
        OnAuthenticated = context =>
        {
            ReplaceExistingClaims(context.Identity);
            return Task.FromResult(0);
        }
    }
};

Above, I know I can hook an individual provider from Startup IF it provides an Authenticated event. I have two conceptual problems with this. One: it requires me to write and wire up my code separately for each provider I plug in. Two: there is no requirement for providers to provide this event. Both of these make me feel like there must be a different intended insertion point for my code.

public ActionResult ExternalLoginCallback(string returnUrl)
{
    ReplaceExistingClaims((ClaimsIdentity)User.Identity);
    new RedirectResult(returnUrl);
}

Above, I know I can put code in ExternalLoginCallback. But this happens too late for two reasons. One: The user has already been issued a ticket I consider invalid, but the default [Authorized] considers valid because it's signed by me, and now they are making requests to my site with it. There could even be race conditions here. Two: There is no guarantee the browser will visit this redirect, and I'd prefer from a design perspective if it didn't have to, e.g. to simplify my WebAPI client code.

To the best of my knowledge, the best solution will meet these requirements:

  1. same code applies to all providers
  2. client receives my custom ticket from my server (e.g. without image claims)
  3. client never receives another ticket format from my server
  4. the authentication process requires the minimum possible HTTP round-trips
  5. token-refresh and other core identity features are still available
  6. once a user is [Authorize]d, no further account transformation is necessary
  7. database/repository access is feasible during ticket generation

Some pages I'm researching, for my own notes:

Community
  • 1
  • 1
shannon
  • 8,664
  • 5
  • 44
  • 74
  • Could you please clarify what is the flow? – dr11 Apr 07 '15 at 13:31
  • Sure. Application Access Attempt -> Facebook/Google Authentication -> Custom Ticket Assignment -> Authorization. This would presumably be followed by: Additional Access Attempt -> Custom Ticket Presentation -> Authorization. The custom ticket will contain the minimum required to accomplish this, while the authentication negotiation may result in other extra data, such as a profile photo. – shannon Apr 07 '15 at 17:50
  • Basically you need to send request to the server to login with any of provider? This request should be async? And after you'll become logged in client should receive notification and related information should be loaded? – dr11 Apr 08 '15 at 08:30
  • @deeptowncitizen : you are asking about the authentication process itself, and I have no problem answering, but I don't think it's really relevant to my question. I'm happy with the process of OAuth2 authentication as it's currently implemented. My question is, what is the correct place/way to provide the user's web browser my custom identity ticket after that authentication process? By default, Identity 2.0 results in a authentication token cookie that is about 100 times the size of my prior token, and currently that's not in my control. – shannon Apr 08 '15 at 14:49

2 Answers2

0

You have to implement DelegationHandler and put all your authentication routines in it.

Register at Application start (DI usage is enabled):

private static void RegisterHandlers(HttpConfiguration config)
{
    var authHandler = new MyFacebookAuthHandler();
    config.MessageHandlers.Add(authHandler);
}

And this is an example of implementation:

public class MyFacebookAuthHandler : DelegationHandler
{
    public override sealed Task<HttpResponseMessage> OnSendAsync(HttpRequestMessage request,
                                                                 CancellationToken cancellationToken)
    {
        try
        {
            // Process credentials
            // Probably you have to save some auth information to HttpContext.Current
            // Or throw NotAuthorizedException
        }
        catch(NotAuthorizedException ex)
        {
            return request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex).ToCompletedTask();
        }
        catch (Exception ex)
        {
            return request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex).ToCompletedTask();
        }

        return base.OnSendAsync(request, cancellationToken);
    }
}
dr11
  • 5,166
  • 11
  • 35
  • 77
  • Thank you for the reply. I'm not certain what you are doing here. I assume that by `DelegationHandler` you actually mean `DelegatingHandler` and `OnSendAsync` is `SendAsync`? If so, I don't see how this helps. I'm looking to modify the claim provided to clients. That should happen once, the moment authentication is completing. They will still need to be authenticated first, and that step will typically require redirecting the client to the external provider. Consequently "credentials" can't really be processed in a MessageHandler. Am I misunderstanding you? – shannon Apr 07 '15 at 13:08
  • After some more research, I see that DelegatingHandler indeed runs every request, similarly to the standard OWIN module I'm already using to get the external authentication. I apologize if I wasn't clear enough in my question. – shannon Apr 10 '15 at 23:23
0

The ClaimsAuthenticationManager class is specifically for this.

https://msdn.microsoft.com/en-us/library/system.security.claims.claimsauthenticationmanager(v=vs.110).aspx

Code sample from that reference:

class SimpleClaimsAuthenticatonManager : ClaimsAuthenticationManager
{
    public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
    {
        if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true)
        {
            ((ClaimsIdentity)incomingPrincipal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
        }
        return incomingPrincipal; 
    }
}
shannon
  • 8,664
  • 5
  • 44
  • 74
  • p.s. I'm glad I came across this, because I was about to implement this as a separate OWIN shim. – shannon Apr 10 '15 at 19:41
  • Also, in that document, "RP" stands for "relying party". Ugh, some technical writer needs to be fired. – shannon Apr 10 '15 at 19:44