302 Redirect in an Authenticated API call even though I'm logged in

I’m having a devil of a time getting this particular call to use the Authenticate tag correctly. Without the tag, it runs through fine, but we want to ensure that the user is logged in before being allowed to create a product review. We assign the created by value to the currently logged in user id from the Membership session which is populated by DotNetNuke when logged in.

In most of our other calls that have the Authenticate tag, it works fine. With this one, I get a 302 Found notice. I know that it’s not my Angular code doing it as I can get the same result using the Advanced REST Client for Chrome App.

As you can see from the below screenshot. I hit the service endpoint, it finds a 302, and tries to send me to the /login.aspx page, which doesn’t exist because this is just an API. If you tried to go to …/API you would get redirected to /API/metadata. There is no HTML content served by any of our endpoints, only content via JSON.

In researching this issue, I found many references by mythz and others on StackOverflow about setting the HtmlRedirectUrl to null in my AuthFeature. As that was already set, I tried changing it to different values for the feature, and RedirectUrls on each auth provider (we use 4 in total, 3 of which are custom) and I even tried an HtmlRedirect on the Authenticate tag itself. None changed the outcome from /login.aspx.

I’m assuming I’m missing something obvious, so here’s some more data to throw at it:

ServiceStack Signed 4.0.40 DLLs. As we work with both DotNetNuke and SiteFinity, SiteFinity requires 4.0.40 Signed right now so we’re stuck on that version (there were a lot of issues trying to use newer/older versions). DNN is version 7.4.0 and this API project is hosted as a Virtual Application under the DNN Website’s IIS instance.

Here’s the C# endpoint class (with an HtmlRedirect on it):

Here’s the Auth Feature setup:

I can provide more as needed.

What’s the Accept: HTTP Request Header say? This is what controls the Response type you’re requesting, the HtmlRedirect only applies to requests from Html (e.g. web page). Any other request should just fail with a 401 without a redirect.

There’s something strange about the redirected url as nothing in ServiceStack redirects to login.aspx and the ReturnUrl isn’t a convention in ServiceStack, it uses ?redirect instead - if you’re not doing it then it sounds like you’re getting hit by ASP.NET Authentication hijacking the 401 response which you should be able to disable in your Web.config with:

<authentication mode="None" />

Or if you’re using .NET 4.5+ you could add a Global Request Filter to set the ASP.NET’s Response.SuppressFormsAuthenticationRedirect property, e.g:

GlobalRequestFilters.Add((req, res, dto) => {
    var aspRes = (HttpResponseBase)res.OriginalResponse;
    aspRes.SuppressFormsAuthenticationRedirect = true;

Response Headers (I cut off the cookie):

Here is the current GlobalRequestFilters add that we have, it’s so that you are required to send the X-ApiKey as either a header or a query string parameter if the request has the Authenticate tag. In the request, we are sending it always because it’s part of the Angular hook we have. Also I added it in to the ARC request from the screenshot.

I had tried placing a breakpoint in this lambda function but it never hits it (or hits it in a way that VS can’t stop in it).

We also have a PathToSuppress in the web config that takes the site root url and appends a specific location so that it will talk to the DNN API (where we have an MVC controller that generates a Token to authenticate/validate).

The url ends up being http://[rooturl]/DesktopModules/…DNN/API and DNN knows to respond to that.

We are using .NET 4.5.2, so I could try the latter suggestion of adding a filter. The web.config seems to inherit some settings from the DNN web config since this is a child virtual application. We’ve had issues in the past with trying to set a location tag in the DNN web.config however in that it borks a lot of other stuff.

Adding the following code below my current request filter and same result of login.aspx

appHost.GlobalRequestFilters.Add((req, res, dto) => {
    var aspRes = (HttpResponseBase)res.OriginalResponse;
    aspRes.SuppressFormsAuthenticationRedirect = true;

Actually that would be too late if it’s being short-circuited by the [Authenticate] attribute since they get executed before the Global Request Filters, can you try a PreRequestFilter instead:

appHost.PreRequestFilters.Add((req, res) => {
    var aspRes = (HttpResponseBase)res.OriginalResponse;
    aspRes.SuppressFormsAuthenticationRedirect = true;

It worked that time, in that now I’m not getting redirected, I stay on the 401.

Now that’s 1 step towards the goal. The question now is, why is it telling me 401 unauthorized when I am successfully logged in and other Authenticate actions are working.

I’m trying it in pages and now it’s really hit or miss if it wants to reply 401, or go through and run the workflow behind the endpoint.

Fiddled with it some more. When I have a full page refresh and go back into a page that has one of those queries on it, it’s consistently returning the query and not a 401 anymore when logged in.

I’ll consider this resolved for the time being until I see it again.

Thanks for your help

Simply speaking, you’ll get a 401 if the Session Cookies sent with the request do not map to an Authenticated UserSession that’s stored in the registered ICacheClient. This is what identifies an “Authenticated Request”, if none or invalid Session Cookies are sent with the request then it’s just an anonymous HTTP Request.

You can override the default behavior with a custom AuthProvider and UserSession. If you have a Custom Auth Provider you can put a breakpoint on AuthProvider.IsAuthorized(), if you’re using a Custom AuthUserSession you can add a break point on AuthUserSession.IsAuthorized() where you can debug to find out why it’s returning false.

If these aren’t getting hit then the Session can’t be resolved, you can then add a breakpoint in a PreRequestFilter to inspect the incoming Request and make sure the ss-id/ss-pid Cookies are valid or override AppHost.OnSessionFilter() which will get called with the resolved Session and sessionId each time a User Session is resolved from the ICacheClient.

Sweet! Thanks, I’ll come back and use the above the next time I see a 401 when I’m not supposed to be and still start debugging.