web-dev-qa-db-fra.com

Authentification basée sur les jetons dans l'API Web sans aucune interface utilisateur

Je développe une API REST dans une API Web ASP.Net. Mon API sera uniquement accessible via des clients non basés sur un navigateur. Je dois implémenter la sécurité pour mon API et j'ai donc décidé de choisir 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 besoin d'aucune interface utilisateur pour la connexion car les informations de connexion seront transmises par le client via HTTP POST qui sera autorisé à partir de notre base de données. Comment puis-je implémenter l'authentification par jeton dans mon API? Veuillez noter que mon API sera accessible en haute fréquence, donc je dois également prendre en charge les performances. S'il vous plaît laissez-moi savoir si je peux l'expliquer mieux.

61
Souvik Ghosh

Je pense qu'il y a une certaine confusion à propos de la différence entre MVC et Web Api. En bref, pour MVC, vous pouvez utiliser un formulaire de connexion et créer une session à l'aide de 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 terminal Token est tout ce dont vous avez besoin. Comme Win l'a décrit, vous allez envoyer les informations d'identification au noeud final du jeton où elles sont traitées.

Voici un 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;
        }
    }

Pour 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ù Token est:

//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 souhaitez une API Web uniquement. J'ai simplifié le code côté serveur pour le rendre plus lisible. Vous pouvez ajouter du code pour mettre à niveau la sécurité.

Assurez-vous d'utiliser uniquement SSL. Implémentez RequireHttpsAttribute pour le forcer.

Vous pouvez utiliser les attributs Authorize/AllowAnonymous pour sécuriser votre Web Api. De plus, vous pouvez ajouter des filtres (tels que RequireHttpsAttribute) pour sécuriser davantage votre API Web. J'espère que ça aide.

76
Ruard van Elburg

L'API Web ASP.Net intègre déjà le 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 de publier le nom d'utilisateur et le mot de passe encodés dans l'URL dans la chaîne de requête.

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

Si vous souhaitez en savoir plus, vous pouvez regarder Enregistrement et connexion de l'utilisateur - Angular Avant-arrière avec API Web de Deborah Kurata .

19
Win