web-dev-qa-db-fra.com

Authentification par jeton Owin Bearer + autorisation du contrôleur

J'essaie de faire l'authentification avec des jetons au porteur et Owin.

Je peux émettre l'amende de jeton en utilisant le type de subvention password et en remplaçant GrantResourceOwnerCredentials dans AuthorizationServerProvider.cs.

Mais je ne peux pas atteindre une méthode de contrôleur avec l'attribut Authorize.

Voici mon code:

Startup.cs

public class Startup
{
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    // normal
    public Startup() : this(false) { }

    // testing
    public Startup(bool isDev)
    {
        // add settings
        Settings.Configure(isDev);

        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/Token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new AuthorizationServerProvider()
        };
    }

    public void Configuration(IAppBuilder app)
    {
        // Configure the db context, user manager and role manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
        app.CreatePerOwinContext<LoanManager>(BaseManager.Create);

        var config = new HttpConfiguration();
        WebApiConfig.Register(config);
        app.UseWebApi(config);

        // token generation
        app.UseOAuthAuthorizationServer(OAuthOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
        {
            AuthenticationType = "Bearer",
            AuthenticationMode = AuthenticationMode.Active
        });
    }
}

AuthorizationServerProvider.cs

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

        IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);

        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim("sub", context.UserName));
        identity.AddClaim(new Claim("role", "user"));

        context.Validated(identity);
    }
}

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        // enable CORS for all hosts, headers and methods
        var cors = new EnableCorsAttribute("*", "*", "*");
        config.EnableCors(cors);

        config.Routes.MapHttpRoute(
            name: "optional params",
            routeTemplate: "api/{controller}"
        );

        config.Routes.MapHttpRoute(
            name: "Default",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        // stop cookie auth
        config.SuppressDefaultHostAuthentication();
        // add token bearer auth
        config.Filters.Add(new MyAuthenticationFilter());
        //config.Filters.Add(new HostAuthenticationFilter(Startup.OAuthOptions.AuthenticationType));

        config.Filters.Add(new ValidateModelAttribute());

        if (Settings.IsDev == false)
        {
            config.Filters.Add(new AuthorizeAttribute());
        }

        // make properties on model camelCased
        var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
        jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
    }

MyAuthenticationFilter.cs Filtre personnalisé utilisé à des fins de débogage

public class MyAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
    public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        if (context.Principal != null && context.Principal.Identity.IsAuthenticated)
        {
        }

        return Task.FromResult(0);
    }

    public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
        throw new System.NotImplementedException();
    }
}

Si je débogue AuthenticateAsync dans MyAuthenticationFilter.cs je vois l'en-tête dans la requête:

Authorization: Bearer AQAAANCMnd8BFdERjHoAwE_Cl...

Mais les revendications d'identité sont vides et context.Principal.Identity.IsAuthenticated c'est faux.

Des idées?

20
Colin

Je cherchais la même solution, j'y ai passé une semaine environ et je l'ai laissée. Aujourd'hui, j'ai recommencé à chercher, j'ai trouvé vos questions et j'espérais trouver une réponse.

J'ai donc passé toute la journée à ne rien faire d'autre que d'essayer toutes les solutions possibles, à fusionner les suggestions les unes avec les autres, j'ai trouvé une solution, mais c'était de longues solutions de contournement, pour faire court, voici ce que j'ai trouvé.

Tout d'abord, si vous devez authentifier le site Web avec un jeton de fournisseur d'identité tiers personnalisé, vous devez les avoir tous les deux en utilisant la même machineKey ou vous devez les avoir tous les deux sur le même serveur.

Vous devez ajouter la machineKey à la system.web section comme suit:

Web.Config

<system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <machineKey validationKey="*****" decryptionKey="***" validation="SHA1" decryption="AES" />
</system.web>

Voici un lien vers générer une nouvelle machineKey :

Vous devez maintenant vous déplacer vers le fichier Startup.Auth.cs où vous pouvez trouver la classe partielle Startup.cs, vous devez définir les OAuthBearerOptions

Startup.Auth.cs

public partial class Startup
{
    public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
    ...

    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and signin manager to use a single instance per    request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
        app.UseOAuthBearerAuthentication(OAuthBearerOptions);
        ...
    }
}

Remplacez votre action de connexion dans AccountController par ce qui suit:

AccountController.cs

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    /*This will depend totally on how you will get access to the identity provider and get your token, this is just a sample of how it would be done*/
    /*Get Access Token Start*/
    HttpClient httpClient = new HttpClient();
    httpClient.BaseAddress = new Uri("https://youridentityproviderbaseurl");
    var postData = new List<KeyValuePair<string, string>>();
    postData.Add(new KeyValuePair<string, string>("UserName", model.Email));
    postData.Add(new KeyValuePair<string, string>("Password", model.Password));
    HttpContent content = new FormUrlEncodedContent(postData);


    HttpResponseMessage response = await httpClient.PostAsync("yourloginapi", content);
    response.EnsureSuccessStatusCode();
    string AccessToken = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync());
    /*Get Access Token End*/

    If(!string.IsNullOrEmpty(AccessToken))
    {
            var ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(AccessToken);
            var id = new ClaimsIdentity(ticket.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
            AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, id);

            return RedirectToLocal(returnUrl);

   }

   ModelState.AddModelError("Error", "Invalid Authentication");
   return View();
}

La dernière chose que vous devez faire est de placer cette ligne de code dans Global.asax.cs pour éviter les exceptions Anti Forgery:

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;

        …
    }
}

J'espère que cela fonctionnera pour vous.

7
Shadi

Un an depuis que cela a été publié, et moi aussi j'ai connu le même problème.

enter image description here

Comme vous pouvez le voir, mon jeton de porteur est reconnu dans les en-têtes de demande, mais mon identité n'est toujours pas authentifiée.

Pour résoudre ce problème, la réponse courte est de configurer votre OAuth avant de configurer votre middleware WebApi (HttpConfiguration) .

4
ad0ran

Eh bien, je travaille sur cela depuis un certain temps maintenant et j'ai finalement compris ce qui ne va pas et maintenant ça fonctionne.

Il semble que votre code d'activation Cors sur la méthode GrantResourceOwnerCredentials remplace en quelque sorte l'en-tête du paramètre. Ainsi, en plaçant votre première ligne juste en dessous de votre troisième actuel, vous aurez résolu votre problème:

    var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

    IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);

   context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

Jusqu'à présent, je n'ai pas creusé plus profondément pour comprendre pourquoi il en est ainsi, mais je crois qu'en ajoutant la nouvelle entrée d'en-tête avant d'obtenir le userManager corrompt en quelque sorte les données envoyées par la méthode post sur le client, dans mon cas, un angular qui ressemble à ceci:

    function userAccount($resource, appSettings) {
    return {
        registration: $resource(appSettings.serverPath + "/api/Account/Register", null, 
                {
                    'registerUser' : { method : 'POST'}
                }
            ),
        login : $resource(appSettings.serverPath + "/Token", null, 
                {
                    'loginUser': {
                        method: 'POST',
                        headers: {
                            'Content-Type' : 'application/x-www-form-urlencoded' 
                        },
                        transformRequest: function (data, headersGetter) {
                            var str = [];
                            for (var d in data) {
                                str.Push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
                            }
                            return str.join("&"); 
                        }
                    }
                } 
            )
    }
}
3
MarcoSantana

Je ne sais pas si cela aide, mais j'ai eu un problème avec IsAuthenticated qui revient faux lors de l'utilisation de l'injection de dépendance (voir SO question ici ) et il a cherché car, à le point d'injection n'a pas été fixé par le pipeline Owin.

Je l'ai surmonté en injectant paresseusement le principal. Dans les deux cas, j'ai mis en place une application vraiment basique (qui est liée à ce qui précède) pour illustrer le problème, mais cela pourrait vous aider car elle montre que le principal est défini dans l'attribut et l'utilisation de l'authentification au porteur.

0
teatime