Adding additional logic to Bearer authorization

后端 未结 3 844
借酒劲吻你
借酒劲吻你 2021-01-30 15:11

I am attempting to implement OWIN bearer token authorization, and based on this article. However, there\'s one additional piece of information I need in bearer token that I don\

相关标签:
3条回答
  • 2021-01-30 15:33

    Just to add on to LeftyX's answer, here's how you can completely control the response being sent to the client once the context is rejected. Pay attention to the code comments.

    Based on Greg P's original answer, with some modifications

    Step1: Create a class which will act as your middleware

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

    namespace 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 : Create the extensions class (Can be omitted).

    This step is optional, can be modified to accept options that can be passed to the 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: Modify GrantResourceOwnerCredentials method in your OAuthAuthorizationServerProvider implementation

    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: Use this middleware in the startup class

    public void Configuration(IAppBuilder app)
    {
        app.UseResponseMiddleware();
    
        //configure the authentication server provider
        ConfigureOAuth(app);
    
        //rest of your code goes here....
    }
    
    0 讨论(0)
  • 2021-01-30 15:34

    On a side note, if you want to set a custom error message you'll have to swap the order of the context.Rejected and 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();
    

    If you place context.Rejected after context.SetError then the property context.HasError will be reset to false therefore the correct way to use it is:

        // Client could not be validated.
        context.Rejected();
        context.SetError("invalid_client", "Client credentials are invalid.");
    
    0 讨论(0)
  • 2021-01-30 15:35

    It seems there's something missing in your code.
    You're not validating your client.

    You should implement ValidateClientAuthentication and check your client's credentials there.

    This is what I do:

    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();
            }
      }
    

    A good article full of details can be found here.
    A even better explanation can be found in this blog series.

    UPDATE:

    I did some digging and webstuff is right.

    In order to pass errorDescription to the client we need to Rejected before we set the error with SetError:

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

    or we can extend it passing a serialized json object in the description:

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

    With a javascript/jQuery client we could deserialize the text response and read the extended message:

    $.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);
                }
        }
    });
    
    0 讨论(0)
提交回复
热议问题