Creación de un proveedor de autenticación Owin que intercambia un token personalizado para una cookie de autenticación .Net

Estoy tratando de crear una solución similar a SSO entre 2 aplicaciones .Net. La aplicación 1 .Net tiene un generador de tokens y puntos finales personalizados para validar tokens que devuelven información del usuario.

La aplicación .Net 2 está protegida con Owin y era una aplicación independiente típica y un usuario iniciaría sesión directamente con una contraseña y nombre de usuario.

Yo creé (basado enBlog de la pasión por la codificación yGithub) un proveedor de Owin personalizado que buscaría un token en un encabezado de Autorización o como un parámetro de consulta desde un enlace en el que un usuario haría clic en un enlace de .Net App 1 y enviaría a la .Net App 2 el token en la cadena de consulta como en GET (sé que esto no es seguro, eventualmente usaremos OpenID para lo que vale, solo necesitamos esto para una demostración). Puedo obtener el token validarlo y crear una identidad y autenticar No puedo hacer que el proveedor cree una cookie de autenticación .Net para que las solicitudes posteriores se autentiquen y no reciban un error 401.

Archivo de controlador:

using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace SomeOAuth
{
// Created by the factory in the someAuthenticationMiddleware class.
class SomeAuthenticationHandler : AuthenticationHandler<SomeAuthenticationOptions>
{
    private const string HandledResponse = "HandledResponse";

    private readonly ILogger _logger;
    private readonly string _challenge;

    /// <summary>
    /// Creates a new OpenIdConnectAuthenticationHandler
    /// </summary>
    /// <param name="logger"></param>
    public SomeAuthenticationHandler(ILogger logger, string challenge)
    {
        _logger = logger;
        _challenge = challenge;
    }


    protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
    {
        // ASP.Net Identity requires the NameIdentitifer field to be set or it won't  
        // accept the external login (AuthenticationManagerExtensions.GetExternalLoginInfo)
        string requestToken = null;
        string authorization = Request.Headers.Get("Authorization");

        if (!string.IsNullOrEmpty(authorization))
        {
            if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
            {
                requestToken = authorization.Substring("Bearer ".Length).Trim();
            }
        }

        if (string.IsNullOrEmpty(requestToken))
        {
            string accessTokenParam = Request.Query.Get("access_token");
            if (!string.IsNullOrEmpty(accessTokenParam))
            {
                requestToken = accessTokenParam;
            }
        }

        if (!string.IsNullOrEmpty(requestToken))
        {
            using (var client = new HttpClient())
            {
                try
                {
                    var request = new HttpRequestMessage(HttpMethod.Post, "https://testserver/API/Auth/Authenticate");
                    var s = new StringContent("{\"oauthtoken\":\"" + requestToken + "\"}", Encoding.UTF8, "application/json");
                    // var ts = s.ToString();
                    request.Content = new StringContent("{\"oauthtoken\":\"" + requestToken + "\"}", Encoding.UTF8, "application/json");

                    System.Diagnostics.Debug.WriteLine("Request:");
                    System.Diagnostics.Debug.WriteLine(request.ToString());
                    if (request.Content != null)
                    {
                        System.Diagnostics.Debug.WriteLine(await request.Content.ReadAsStringAsync());
                    }
                    System.Diagnostics.Debug.WriteLine("");

                    var response = await client.SendAsync(request);
                    if (response.StatusCode != HttpStatusCode.OK)
                    {
                        return null;
                    }

                    var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
                    var userId = payload.Value<string>("username");

                    //need to get the useid of the user as well as the name and role

                    var identity = new ClaimsIdentity("Some");
                    identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "fakeuser", null, "Some"));
                    /*
                    identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName + " " + user.LastName));
                    identity.AddClaim(new Claim(ClaimTypes.Email, user.ContactInfo.Email));
                    identity.AddClaim(new Claim(ClaimTypes.Sid, user.Guid.ToString()));
                    */
                    identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Some"));
                    AuthenticationProperties properties = CreateProperties("fakeusername", "");
                    var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
                    return ticket;
                }
                catch (Exception e)
                {
                    Console.WriteLine("asdf e = " + e.Message);
                }
                return null;
            }
        }
        else
        {
            return null;
        }
    }

    /// <summary>
    /// Handles SignIn
    /// </summary>
    /// <returns></returns>
    protected override Task ApplyResponseChallengeAsync()
    {

        if (Response.StatusCode == 401)
        {
            AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
            if (challenge == null)
            {
                return null;
            }
        }

        return Task.FromResult<object>(null);
    }




    public override Task<bool> InvokeAsync()
    {
        return InvokeReplyPathAsync();
    }

    private async Task<bool> InvokeReplyPathAsync()
    {
        AuthenticationTicket ticket = await AuthenticateAsync();

        if (ticket != null)
        {
            string value;
            if (ticket.Properties.Dictionary.TryGetValue(HandledResponse, out value) && value == "true")
            {
                return true;
            }
            if (ticket.Identity != null)
            {
                Request.Context.Authentication.SignIn(ticket.Properties, ticket.Identity);
            }
            // Redirect back to the original secured resource, if any.
            if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
            {
                Response.Redirect(ticket.Properties.RedirectUri);
                return true;
            }
        }

        return false;
    }

    private static AuthenticationTicket GetHandledResponseTicket()
    {
        return new AuthenticationTicket(null, new AuthenticationProperties(new Dictionary<string, string>() { { HandledResponse, "true" } }));
    }

    public AuthenticationProperties CreateProperties(string userName, string Roles)
    {
        IDictionary<string, string> data = new Dictionary<string, string>
    {
        { "userName", userName },
        {"roles",Roles}
    };
        return new AuthenticationProperties(data);
    }

  }
}

Archivo de middleware:

using Microsoft.Owin;
using Microsoft.Owin.Security.Infrastructure;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataProtection;
using Microsoft.Owin.Security.DataHandler;
using Microsoft.Owin.Logging;

namespace SomeOAuth
{
    // One instance is created when the application starts.
    public class SomeeAuthenticationMiddleware : AuthenticationMiddleware<SomeAuthenticationOptions>
    {
        private readonly ILogger _logger;
        private readonly string _challenge = "Bearer";

        public SomeAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SomeAuthenticationOptions options)
            : base(next, options)
        {

            _logger = app.CreateLogger<SomeAuthenticationMiddleware>();


        }

        // Called for each request, to create a handler for each request.
        protected override AuthenticationHandler<SomeAuthenticationOptions> CreateHandler()
        {
            return new SomeAuthenticationHandler(_logger, _challenge);
        }
    }
}

Archivo de opciones:

using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SomeOAuth
{
    public class SomeAuthenticationOptions : AuthenticationOptions
    {
        public SomeAuthenticationOptions(string userName, string userId)
            : base(OAuthDefaults.AuthenticationType)
        {
            UserName = userName;
            UserId = userId;
        }

        public string Challenge { get; set; }

        public string UserName { get; set; }

        public string UserId { get; set; }

    }
}

Archivo de extensiones:

using Microsoft.Owin.Extensions;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SomeOAuth
{

    public static class SomeAuthenticationExtensions
    {
        public static IAppBuilder UseSomeeAuthentication(this IAppBuilder app, SomeAuthenticationOptions options)
        {
            if (app == null)
            {
                throw new ArgumentNullException("app");
            }

            app.Use(typeof(SomeAuthenticationMiddleware), app, options);
            app.UseStageMarker(PipelineStage.Authenticate);

            return app;
        }
    }
}

Archivo de inicio

using System;
using CoreLX.Palms.VS.Web.Services;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin.Security.Providers.OpenID;
using Microsoft.Owin.Security.OAuth;
using Owin;
using SomeOAuth;
using CoreLX.Palms.LS.Web.Common.Models.User;

namespace CoreLX.Palms.VS.Web
{
    public partial class Startup
    {

        public void ConfigureAuth(IAppBuilder app)
        {

            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>    (ApplicationSignInManager.Create);

            //app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
            //{
            //    AccessTokenProvider = new SomeTokenProvider(),
            //    Provider = new SomeOAuthBearerAuthenticationProvider("access_token")
            //});

            app.UseSomeAuthentication(new SomeAuthenticationOptions("testuser", "9"));

            // Use a cookie to temp store information about a user     logging in with a third party login provider
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Enable the application to use a cookie to store information for the signed in user
            app.UseCookieAuthentication(
                new CookieAuthenticationOptions
                {
                    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                    LoginPath = new PathString("/Account/Login"),
                    ExpireTimeSpan = new TimeSpan(0, 3, 0, 0),
                    Provider = new CookieAuthenticationProvider
                    {
                        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
                        OnApplyRedirect = ctx =>
                                {
                                // don't redirect to login page for webapi/ajax requests
                                // http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
                                if (!IsWebApiRequest(ctx.Request))
                                {
                                    ctx.Response.Redirect(ctx.RedirectUri);
                                }
                            }
                    }
                });



            app.UseOpenIDAuthentication("http://me.yahoo.com/", "Yahoo");



        }


        private static bool IsWebApiRequest(IOwinRequest request)
        {
            // hack for check if it's webapi requesr
            if (request.Path.StartsWithSegments(new PathString("/api")))
            {
            return true;
            }

            // checks if it's ajax request
            IReadableStringCollection query = request.Query;
            if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest"))
            {
                return true;
            }
            IHeaderDictionary headers = request.Headers;
            return ((headers != null) && (headers["X-Requested-With"] ==     "XMLHttpRequest"));
    }
    }
}

También he tratado de usar los proveedores personalizados para el estándar proporcionado

OAuthBearerAuthenticationProvider

Aquí está el código para los complementos / proveedores que probé. No tengo preferencia mientras no haya errores 401:

Proveedor

using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SomeOAuth
{
    public class SomeOAuthBearerAuthenticationProvider : IOAuthBearerAuthenticationProvider
    {
        readonly string _parameterName;
        public SomeOAuthBearerAuthenticationProvider(string parameterName)
        {
            _parameterName = parameterName;
        }
        public Task ApplyChallenge(OAuthChallengeContext context)
        {
            return Task.FromResult<object>(null);
        }

        public Task RequestToken(OAuthRequestTokenContext context)
        {
            string token = context.Token;
            if(string.IsNullOrEmpty(token) &&     !string.IsNullOrEmpty(_parameterName))
            {
                token = context.Request.Query.Get(_parameterName);
            }

            if (!string.IsNullOrEmpty(token))
            {
                context.Token = token;
            }

            return Task.FromResult<object>(null);
        }

        public Task ValidateIdentity(OAuthValidateIdentityContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }
    }
}

Y el AccessTokenProvider

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Newtonsoft.Json.Linq;
using System;
//using Newtonsoft.Json.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;


namespace SomeOAuth
{
    public sealed class SomeTokenProvider : AuthenticationTokenProvider
    {
        public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            using (var client = new HttpClient())
            {
                try
                {
                    var request = new HttpRequestMessage(HttpMethod.Post, "https://someserver/API/Auth/Authenticate");
                    var s = new StringContent("{\"oauthtoken\":\"" + context.Token + "\"}", Encoding.UTF8, "application/json");
                    // var ts = s.ToString();
                    request.Content = new StringContent("{\"oauthtoken\":\"" + context.Token + "\"}", Encoding.UTF8, "application/json");

                    System.Diagnostics.Debug.WriteLine("Request:");
                    System.Diagnostics.Debug.WriteLine(request.ToString());
                    if (request.Content != null)
                    {
                        System.Diagnostics.Debug.WriteLine(await request.Content.ReadAsStringAsync());
                    }
                    System.Diagnostics.Debug.WriteLine("");

                    var response = await client.SendAsync(request);
                    if (response.StatusCode != HttpStatusCode.OK)
                    {
                        return;
                    }

                    var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
                    var userId = payload.Value<string>("username");

                    //need to get the useid of the user as well as the name and role

                    var identity = new ClaimsIdentity("Some");
                    identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "someuser", null, "Some"));
                /*
                    identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName + " " + user.LastName));
                    identity.AddClaim(new Claim(ClaimTypes.Email, user.ContactInfo.Email));
                    identity.AddClaim(new Claim(ClaimTypes.Sid, user.Guid.ToString()));
                    */
                    identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Some"));
                    context.SetTicket(new AuthenticationTicket(identity, new AuthenticationProperties()));
                }
                catch (Exception e)
                {
                    Console.WriteLine("asdf e = " + e.Message);
                }
            }
        }
    }
}

Respuestas a la pregunta(1)

Su respuesta a la pregunta