70 votes

Authentification par jeton dans l'API Web sans interface utilisateur

Je suis en train de développer une API REST en ASP.Net Web API. Mon API ne sera accessible que par des clients non basés sur un navigateur. Je dois mettre en œuvre la sécurité de mon API et j'ai donc décidé d'opter pour l'authentification par jeton. J'ai une bonne compréhension de l'authentification par jeton et j'ai lu quelques tutoriels, mais ils ont tous une interface utilisateur pour la connexion. Je n'ai pas besoin d'interface utilisateur pour la connexion car les informations de connexion seront transmises par le client via HTTP POST et seront autorisées à partir de notre base de données. Comment puis-je mettre en œuvre l'authentification par jeton dans mon API ? Veuillez noter que l'accès à mon API sera très fréquent et que je dois donc veiller aux performances. Veuillez me faire savoir si je peux mieux vous expliquer.

92voto

Ruard van Elburg Points 4238

Je pense qu'il y a une certaine confusion sur la différence entre MVC et Web Api. En bref, pour MVC, vous pouvez utiliser un formulaire de connexion et créer une session en utilisant des cookies. Pour Web Api, il n'y a pas de session. C'est pourquoi vous voulez utiliser le jeton.

Vous n'avez pas besoin d'un formulaire de connexion. Le point de terminaison Token est tout ce dont vous avez besoin. Comme Win l'a décrit, vous enverrez les informations d'identification au point de terminaison Token où elles seront traitées.

Voici du code C# côté client pour obtenir un jeton :

    //using System;
    //using System.Collections.Generic;
    //using System.Net;
    //using System.Net.Http;
    //string token = GetToken("https://localhost:<port>/", userName, password);

    static string GetToken(string url, string userName, string password) {
        var pairs = new List<KeyValuePair<string, string>>
                    {
                        new KeyValuePair<string, string>( "grant_type", "password" ), 
                        new KeyValuePair<string, string>( "username", userName ), 
                        new KeyValuePair<string, string> ( "Password", password )
                    };
        var content = new FormUrlEncodedContent(pairs);
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        using (var client = new HttpClient()) {
            var response = client.PostAsync(url + "Token", content).Result;
            return response.Content.ReadAsStringAsync().Result;
        }
    }

Afin d'utiliser le jeton, ajoutez-le à l'en-tête de la demande :

    //using System;
    //using System.Collections.Generic;
    //using System.Net;
    //using System.Net.Http;
    //var result = CallApi("https://localhost:<port>/something", token);

    static string CallApi(string url, string token) {
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        using (var client = new HttpClient()) {
            if (!string.IsNullOrWhiteSpace(token)) {
                var t = JsonConvert.DeserializeObject<Token>(token);

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + t.access_token);
            }
            var response = client.GetAsync(url).Result;
            return response.Content.ReadAsStringAsync().Result;
        }
    }

Où se trouve Token :

//using Newtonsoft.Json;

class Token
{
    public string access_token { get; set; }
    public string token_type { get; set; }
    public int expires_in { get; set; }
    public string userName { get; set; }
    [JsonProperty(".issued")]
    public string issued { get; set; }
    [JsonProperty(".expires")]
    public string expires { get; set; }
}

Maintenant, pour le côté serveur :

Dans Startup.Auth.cs

        var oAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider("self"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            // https
            AllowInsecureHttp = false
        };
        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(oAuthOptions);

Et dans ApplicationOAuthProvider.cs, le code qui accorde ou refuse l'accès :

//using Microsoft.AspNet.Identity.Owin;
//using Microsoft.Owin.Security;
//using Microsoft.Owin.Security.OAuth;
//using System;
//using System.Collections.Generic;
//using System.Security.Claims;
//using System.Threading.Tasks;

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
    private readonly string _publicClientId;

    public ApplicationOAuthProvider(string publicClientId)
    {
        if (publicClientId == null)
            throw new ArgumentNullException("publicClientId");

        _publicClientId = publicClientId;
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

        var user = await userManager.FindAsync(context.UserName, context.Password);
        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager);
        var propertyDictionary = new Dictionary<string, string> { { "userName", user.UserName } };
        var properties = new AuthenticationProperties(propertyDictionary);

        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        // Token is validated.
        context.Validated(ticket);
    }

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }
        return Task.FromResult<object>(null);
    }

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        // Resource owner password credentials does not provide a client ID.
        if (context.ClientId == null)
            context.Validated();

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

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    {
        if (context.ClientId == _publicClientId)
        {
            var expectedRootUri = new Uri(context.Request.Uri, "/");

            if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                context.Validated();
        }
        return Task.FromResult<object>(null);
    }

}

Comme vous pouvez le constater, aucun contrôleur n'est impliqué dans la récupération du jeton. En fait, vous pouvez supprimer toutes les références MVC si vous voulez une Web Api uniquement. J'ai simplifié le code côté serveur pour le rendre plus lisible. Vous pouvez ajouter du code pour améliorer la sécurité.

Veillez à n'utiliser que le protocole SSL. Mettez en œuvre l'attribut RequireHttpsAttribute pour le forcer.

Vous pouvez utiliser les attributs Authorize / AllowAnonymous pour sécuriser votre Web Api. En outre, vous pouvez ajouter des filtres (comme RequireHttpsAttribute) pour rendre votre API Web plus sûr. J'espère que cela vous aidera.

22voto

Win Points 16724

L'API Web ASP.Net intègre déjà un serveur d'autorisation. Vous pouvez le voir à l'intérieur Startup.cs lorsque vous créez une nouvelle application Web ASP.Net avec un modèle d'API Web.

OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // In production mode set AllowInsecureHttp = false
    AllowInsecureHttp = true
};

Tout ce que vous avez à faire est d'afficher le nom d'utilisateur et le mot de passe codés par URL dans la chaîne de requête.

/Token/userName=johndoe%40example.com&password=1234&grant_type=password

Si vous voulez connaître plus de détails, vous pouvez regarder Inscription et connexion des utilisateurs - Angular Front to Back avec Web API par Deborah Kurata .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X