3

Situation: I return Forbid() from the PageModel and this initiates response with Redirect (302) to /AccessDenied&returnUrl=...

Here I specify the path:

 // configuration was used
 serviceCollection.ConfigureApplicationCookie(options =>
 {
     options.AccessDeniedPath = new PathString("/AccessDenied");
 });

But how can I totally override the "redirect response" creation and specify my own GET parameters (something as returnUrl) or redefine the returnUrl value (I want to pass my own value that is saved in custom request feature)?

Alternatively instead of returning Forbid() I can redirect to AccessDenied manually. But I would like to stay with Forbid() for demonstrativeness and consistency.

UPDATE: this doesn't work either

public void ConfigureServices(IServiceCollection serviceCollection)
    {
        serviceCollection.ConfigureApplicationCookie(options =>
        {
            //options.AccessDeniedPath = new PathString("/AccessDenied");
            options.Events.OnRedirectToAccessDenied = context =>
            {
                context.Response.Redirect("/AccessDenied&t=100");
                return Task.CompletedTask;
            };
        });

        // need for password reset functionality
        serviceCollection.AddDbContext<WebUserDbContext>(optionsBuilder =>
            optionsBuilder.UseSqlServer(ApplicationSettings.WebUserStorageConfiguration.ConnectionString));

        serviceCollection.AddIdentity<WebUser, IdentityRole<int>>( options =>
            {
                options.SignIn.RequireConfirmedEmail = true;
            })
            .AddEntityFrameworkStores<WebUserDbContext>()
            .AddDefaultTokenProviders();

        // generally it is a version of routin by 

         serviceCollection.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

P.S. This is service site that do not need identity to authenticate but manage users records. So MS Identity Lib authentication is not enabled for the site. Identity types just were putted to the container because I need specific Identity API functionality: "reset password". It is idiotic situation: UserManager alike Identity API classes are so complex that it is impossible to cunstruct them other way than get from DI/container. Please do not share this MS DI madness in your APIs.

It is interesting that without serviceCollection.AddIdentity browser recieve "Error 500" on .Forbid() and breakpoint still doesn't stop on context.Response.Redirect("/AccessDenied&t=100");

Roman Pokrovskij
  • 9,449
  • 21
  • 87
  • 142

1 Answers1

2

The redirect to the configured AccessDeniedPath is the default behavior for the cookie authentication. It will automatically add a returnUrl parameter which defaults to the current URL. If you specify a RedirectUri inside of the authentication properties, that URL will be used instead.

For example, inside of a controller:

return Forbid(new AuthenticationProperties
{
    RedirectUri = Url.Action("ActionAfterForbid"),
});

This will basically generate a redirect to the URL {AccessDeniedPath}?returnUrl=/exampleController/ActionAfterForbid.

If you need more customization, you can also override the RedirectToAccessDenied event of the cookie authentication options:

services.ConfigureApplicationCookie(options =>
{
     options.Events.OnRedirectToAccessDenied = context =>
     {
         // context.Options.AccessDeniedPath would be the configured path
         // context.RedirectUri is the passed or automatically set up redirect URI
         // context.HttpContext is the HttpContext if you want to modify features

         // default behavior is basically this:
         context.Response.Redirect(context.RedirectUri);
         return Task.CompletedTask;
     };
});

There, you can do whatever else you want to do.

Note that you will have to call ConfigureApplicationCookie after the call to AddIdentity. This is because the latter will configure the application cookie itself already with some default values, so if you configure the authentication events, they will be overwritten by AddIdentity. Instead, you want to overwrite the defaults with ConfigureApplicationCookie so you have to call that one afterwards.

poke
  • 369,085
  • 72
  • 557
  • 602
  • Thank you! `AuthenticationProperties` works but the more "promissed" event handling with OnRedirectToAccessDenied doesn't. Execution just never get inside. Tested both with .Forbid and Unathorized. What kind of book you can recommend to understand the asp core "internals"? – Roman Pokrovskij Jun 09 '19 at 22:06
  • BTW question aside: Is this an error in naming that HTTP uses "401 Unauthorized" to request the authentication (means login)? IMHO request of login should be named "401 Unathenticated"... Or I miss something? – Roman Pokrovskij Jun 09 '19 at 22:08
  • `Forbid` sets a “403 Forbidden” status and triggers the `RedirectToAccessDenied` event. `Unauthorized` sets a “401 Unauthorized” and triggers the `RedirectToLogin` event. The “Unauthorized” for that 401 is part of the HTTP standard; you do not need to take the naming that strictly. – poke Jun 10 '19 at 13:51
  • Since you say that the event doesn’t work, can you confirm that it actually runs when you put a breakpoint in there and debug your application? Also, do you have other authentication schemes set up, or do you just use the standard Identity setup? Can you share your `Startup.ConfigureServices` code maybe? – poke Jun 10 '19 at 13:52
  • Breakpoint doesn't stop inside event handler on ` context.Response.Redirect("/AccessDenied&t=100");` Full ConfigureServices added to q. It is interesting that when I comment out serviceCollection.AddDbContext the browser on Forbid() receive error 500. – Roman Pokrovskij Jun 11 '19 at 00:19
  • read `serviceCollection.AddIdentity` instead of `serviceCollection.AddDbContext ` – Roman Pokrovskij Jun 11 '19 at 00:33
  • Can you try moving the `ConfigureApplicationCookie` call _after_ the `AddIdentity` call please? It’s possible that the latter will overwrite your events configuration. – And btw.: Whenever there’s a 500, the server threw an exception. You can see that in the server logs which should give you a clear idea on what’s going on (in your case when you remove the `AddIdentity` likely that there isn’t a default authentication scheme configured). – poke Jun 11 '19 at 07:01
  • You are right: "swapping" AddIdentity and ConfigureApplicationCookie works. Poke, how could you know this? Please write a book. Or recommend one. – Roman Pokrovskij Jun 11 '19 at 23:15
  • What is "default authentication scheme" and why without it server fails on `pageModel.Forbid()`? It is so unexpected. – Roman Pokrovskij Jun 11 '19 at 23:19
  • Basically, the default authentication scheme is the authentication scheme that runs when the authentication middleware (which you register with `app.UseAuthentication()`) runs. With Identity, the default scheme is the (application) cookie scheme which basically means that the authentication middleware will attempt to authenticate the user using the information in the cookie. – When there’s no default authentication scheme configured, the middleware will fail to execute. For some detail on default schemes, check [this answer](https://stackoverflow.com/a/52493428/216074). – poke Jun 12 '19 at 01:07
  • What could be a role of the defaul athentication scheme or "middleware" when the authentication protocol (basic, ntlm etc) are not specified? – Roman Pokrovskij Jun 12 '19 at 14:15
  • btw by the fact I use ntlm authentication (after installation to IIS) and it still works even without `UseAuthentication` call ? It is because only of ` serviceCollection.AddIdentity`? That was added do not provide authentication but because of accessing reset password functionality.. Interesting... Abstraction leak that neutralize other abstraction leak. – Roman Pokrovskij Jun 12 '19 at 15:05
  • That’s the point: If you don’t specify a (default) authentication scheme, then the authentication middleware doesn’t know what it should do. As for `AddIdentity`, that actually implicitly also calls `AddAuthentication` and sets up authentication schemes. – poke Jun 12 '19 at 16:22