web-dev-qa-db-fra.com

Ajout de logique supplémentaire à l'autorisation du porteur

Je tente d'implémenter l'autorisation de jeton de porteur OWIN, et sur la base de cet article . Cependant, il me faut une information supplémentaire dans le jeton porteur que je ne sais pas comment mettre en œuvre.

Dans mon application, je dois déduire des informations d'utilisateur du jeton porteur (par exemple, id_utilisateur). Ceci est important car je ne veux pas qu'un utilisateur autorisé puisse agir en tant qu'autre utilisateur. Est-ce faisable? Est-ce même la bonne approche? Si l'ID utilisateur est un GUID, cela serait simple. C'est un entier dans ce cas. Un utilisateur autorisé peut potentiellement usurper l'identité d'un autre utilisateur simplement en devinant/en utilisant la force brutale, ce qui est inacceptable. 

En regardant ce code:

public void ConfigureOAuth(IAppBuilder app)
{
    OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = new SimpleAuthorizationServerProvider()
    };

    // Token Generation
    app.UseOAuthAuthorizationServer(OAuthServerOptions);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}

public class SimpleAuthorizationServerProvider : 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[] { "*" });

        using (AuthRepository _repo = new AuthRepository())
        {
            IdentityUser user = await _repo.FindUser(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);
    }
}

Je pense qu’il est possible de passer outre l’autorisation/authentification pour répondre à mes besoins?

16
Echiban

Il semble qu'il manque quelque chose dans votre code.
Vous ne validez pas votre client.

Vous devez implémenter ValidateClientAuthentication } _ et vérifier les informations d'identification de votre client. 

C'est ce que je fais:

public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
        string clientId = string.Empty;
        string clientSecret = string.Empty;

        if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) 
        {
            context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
            context.Rejected();
            return;
        }

        ApplicationDatabaseContext dbContext = context.OwinContext.Get<ApplicationDatabaseContext>();
        ApplicationUserManager userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

        if (dbContext == null)
        {
            context.SetError("server_error");
            context.Rejected();
            return;
        }

        try
        {
            AppClient client = await dbContext
                .Clients
                .FirstOrDefaultAsync(clientEntity => clientEntity.Id == clientId);

            if (client != null && userManager.PasswordHasher.VerifyHashedPassword(client.ClientSecretHash, clientSecret) == PasswordVerificationResult.Success)
            {
                // Client has been verified.
                context.OwinContext.Set<AppClient>("oauth:client", client);
                context.Validated(clientId);
            }
            else
            {
                // Client could not be validated.
                context.SetError("invalid_client", "Client credentials are invalid.");
                context.Rejected();
            }
        }
        catch (Exception ex)
        {
            string errorMessage = ex.Message;
            context.SetError("server_error");
            context.Rejected();
        }
  }

Un bon article plein de détails peut être trouvé ici .
Une explication encore meilleure peut être trouvée dans cette série blog

METTRE &AGRAVE; JOUR:

J'ai fait des recherches et webstuff a raison.

Afin de transmettre errorDescription au client, nous devons rejeter avant de définir l'erreur avec SetError:

context.Rejected();
context.SetError("invalid_client", "The information provided are not valid !");
return;

ou on peut l'étendre en passant un objet json sérialisé dans la description: 

context.Rejected();
context.SetError("invalid_client", Newtonsoft.Json.JsonConvert.SerializeObject(new { result = false, message = "The information provided are not valid !" }));
return;

enter image description here

Avec un client javascript/jQuery, nous pourrions désérialiser la réponse en texte et lire le message étendu:

$.ajax({
    type: 'POST',
    url: '<myAuthorizationServer>',
    data: { username: 'John', password: 'Smith', grant_type: 'password' },
    dataType: "json",
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    xhrFields: {
        withCredentials: true
    },
    headers: {
        'Authorization': 'Basic ' + authorizationBasic
    },  
    error: function (req, status, error) {
            if (req.responseJSON && req.responseJSON.error_description)
            {
               var error = $.parseJSON(req.responseJSON.error_description);
                    alert(error.message);
            }
    }
});
18
LeftyX

En passant, si vous souhaitez définir un message d'erreur personnalisé, vous devez permuter l'ordre des context.Rejected et context.SetError.

    // Summary:
    //     Marks this context as not validated by the application. IsValidated and HasError
    //     become false as a result of calling.
    public virtual void Rejected();

Si vous placez context.Rejected après context.SetError, alors la propriété context.HasError sera réinitialisée sur false et la méthode correcte pour l'utiliser est la suivante:

    // Client could not be validated.
    context.Rejected();
    context.SetError("invalid_client", "Client credentials are invalid.");
10
webStuff

Pour ajouter à la réponse de LeftyX, voici comment vous pouvez contrôler complètement la réponse envoyée au client une fois le contexte rejeté. Faites attention aux commentaires du code.

Basé sur la réponse originale de Greg P ​​ , avec quelques modifications

Etape 1: Créez une classe qui agira comme votre middleware

using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, System.Object>,
System.Threading.Tasks.Task>;

espace de noms SignOnAPI.Middleware.ResponseMiddleware {

public class ResponseMiddleware 
{
    AppFunc _next;
    ResponseMiddlewareOptions _options;

    public ResponseMiddleware(AppFunc nex, ResponseMiddlewareOptions options)
    {
        _next = next;
    }

    public async Task Invoke(IDictionary<string, object> environment)
    {
        var context = new OwinContext(environment);

        await _next(environment);

        if (context.Response.StatusCode == 400 && context.Response.Headers.ContainsKey("Change_Status_Code"))
        {
            //read the status code sent in the response
            var headerValues = context.Response.Headers.GetValues("Change_Status_Code");

            //replace the original status code with the new one
            context.Response.StatusCode = Convert.ToInt16(headerValues.FirstOrDefault());

            //remove the unnecessary header flag
            context.Response.Headers.Remove("Change_Status_Code");
        }
    }
}

Step2: Créez la classe d'extensions (peut être omis). 

Cette étape est facultative et peut être modifiée pour accepter les options pouvant être transmises au middleware.

public static class ResponseMiddlewareExtensions
{
    //method name that will be used in the startup class, add additional parameter to accept middleware options if necessary
    public static void UseResponseMiddleware(this IAppBuilder app)
    {
        app.Use<ResponseMiddleware>();
    }
}

Step3: Modifiez la méthode GrantResourceOwnerCredentials dans votre implémentation OAuthAuthorizationServerProvider 

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {

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

        if (<logic to validate username and password>)
        {
            //first reject the context, to signify that the client is not valid
            context.Rejected();

            //set the error message
            context.SetError("invalid_username_or_password", "Invalid userName or password" );

            //add a new key in the header along with the statusCode you'd like to return
            context.Response.Headers.Add("Change_Status_Code", new[] { ((int)HttpStatusCode.Unauthorized).ToString() }); 
            return;
        }
    }

Step4: Utilisez ce middleware dans la classe de démarrage

public void Configuration(IAppBuilder app)
{
    app.UseResponseMiddleware();

    //configure the authentication server provider
    ConfigureOAuth(app);

    //rest of your code goes here....
}
0
Giridhar Karnik