简体   繁体   中英

How to check access_denied on client when using IdentityServer4?

I have a login page, and I need when the user clicks Cancel button to redirect him to access denied page on a client application.

Inside login action:

 if (button != "login")
        {
            // the user clicked the "cancel" button
            var context = await interaction.GetAuthorizationContextAsync(model.ReturnUrl);
            if (context != null)
            {
                // if the user cancels, send a result back into IdentityServer as if they 
                // denied the consent (even if this client does not require consent).
                // this will send back an access denied OIDC error response to the client.
                await interaction.GrantConsentAsync(context, ConsentResponse.Denied);

                // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
                return Redirect(model.ReturnUrl);
            }
        }

and on the client-side(MVC) I have configured the following event:

options.Events = new OpenIdConnectEvents
                {
                    OnRemoteFailure = context => 
                    {
                        // here it's returned as 200 ok in case I denied 
                        // consent should'nt be 401 access denined??
                        var statusCode=context.Response.StatusCode;
                        context.Response.Redirect("/");
                        context.HandleResponse();

                        return Task.FromResult(0);
                    }
                };

But my question is: how do I know that the IdentityServer4 has failed because the user clicked the Cancel button(access_denied) or if there is another issue caused that failure?

On the IdentityServer side:

The basic form has 2 buttons: login and cancel . If login is not pressed; it's a cancel .

Otherwise its a validation error and you can show it. On cancel you should redirect back to a page that makes sense.

On the MVC side:

You can redirect with extra parameters. These can be fetched and used to display the error. Keep in mind that a lot of error handling, like invalid username/passord stays at the IdentityServer side.

How to check access_denied on client when using IdentityServer4?

After some inspection, the Event's context.Failure.Data is a type of ListDictionaryInternal , which contains the entries you're probably looking for:

  • error
  • error_description
  • error_uri .

So by using context.Failure.Data["error"] you can get the "access_denied" value you're looking for.


Another Option

Using Identity Server 4's demo code as an example, if we go to the Login Post Account Controller action, the section that would interest you is the one that handles the cancel button press:

// check if we are in the context of an authorization request
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);

// the user clicked the "cancel" button
if (button != "login")
{
    if (context != null)
    {
        // RATHER THAN USING THE ORIGINAL STRATEGY:
        //await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);

        // ...MANUALLY BUILD THE RETURN URL YOURSELF 
        // THEN REDIRECT TO THAT:
        model.ReturnUrl = BuildCancelReturnUrl(context);

        if (await _clientStore.IsPkceClientAsync(context.ClientId))
        {
            return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
        }

        return Redirect(model.ReturnUrl);
    }
    else
    {
        // since we don't have a valid context, then we just go back to the home page
        return Redirect("~/");
    }
}

As you can see, instead of using the call to GrantConsentAsync to respond with the access_denied code (which doesn't include any error_description or error_uri values), you could simply replace the return url with the redirect_uri, adding the error parameter yourself. The trick here is that since the error_description and error_uri parameters are completely optional, you could capitalize on that and pass along something meaningful so that each client knows that this access was denied because the user canceled the form . Then, use that return url in the redirect.

Here's an example on how to "re-build" the return url. You could have a function like:

private string BuildCancelReturnUrl(AuthorizationRequest context)
{
    var RedirectUri = new UriBuilder(context.RedirectUri);
    var Query = HttpUtility.ParseQueryString(string.Empty);

    Query.Add("error", "access_denied");
    Query.Add("error_description", "some_meaningful_code_here");
    // The state IS MEGA IMPORTANT:
    Query.Add("state", context.Parameters["state"]);

    RedirectUri.Query = Query.ToString();

    return RedirectUri.ToString();
}

// Use like:  model.ReturnUrl = BuildCancelReturnUrl(context);
// See above extract from Identity Server's demo code

So then, on the client's side, you could create some extension like this:

public static class OpenIdConnectOptionsExtensions {
    public static OpenIdConnectOptions UseRedirectionOnLoginCancel(
        this OpenIdConnectOptions options
        , string RedirectTo = "/")
    {
        options.Events.OnAccessDenied = context =>
        {
            NameValueCollection RequestQuery = HttpUtility.ParseQueryString(context.Request.QueryString.Value);

            string descriptionField = "error_description";
            string cancelledCode = "your_meaningful_description_code_here";

            bool descriptionIncluded = RequestQuery.AllKeys.Contains(descriptionField);

            if (descriptionIncluded && RequestQuery[errorDescriptionField].Equals(cancelledCode))
            {
                context.Response.Redirect(RedirectTo);
                context.HandleResponse();
            }

            return Task.CompletedTask;
        };

        return options;
    }
}

Finally, each client could be configured to redirect to where you specify in Startup.cs :

services.AddOpenIdConnect(<challenge_name>, config =>
{
    // ...
    config.UseRedirectionOnLoginCancel(<final path to redirect to>);
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM