ASP.NET OWIN callbackURL "/signin-auth0" cannot be found

I’m having trouble getting the .NET (OWIN) MVC authentication example in our website project (.NET Framework 4.5.2). I downloaded and got working the source code example provided at:
https://auth0.com/docs/quickstart/webapp/aspnet-owin/04-authorization

I get a 404 “The resource cannot be found.” error for the callback URL /signin-auth0

I tried to get around this by setting “CallbackPath” in Auth0AuthenticationOptions() to a path that exists. This works, the authentication registers as successful in Auth0 logs, however User.Identity.isAuthenticated returns false after login. There are no errors so I’m lost as to what to look for.

Does anyone have any insights on where I might be going wrong? Bearing in mind that the test project worked.

Many thanks.

Can you please show me the code for your Startup class (when it gives the 404 error)?

There you go. Thank you.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using Auth0.Owin;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Newtonsoft.Json.Linq;
using Owin;

[assembly: OwinStartup(typeof(website.Startup))]

namespace website
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Configure Auth0 parameters
            string auth0Domain = ConfigurationManager.AppSettings["auth0:Domain"];
            string auth0ClientId = ConfigurationManager.AppSettings["auth0:ClientId"];
            string auth0ClientSecret = ConfigurationManager.AppSettings["auth0:ClientSecret"];

            // Enable Kentor Cookie Saver middleware
            app.UseKentorOwinCookieSaver();

            // Set Cookies as default authentication type
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                LoginPath = new PathString("/bp/login")
            });

            var options = new Auth0AuthenticationOptions()
            {
                Domain = auth0Domain,
                ClientId = auth0ClientId,
                ClientSecret = auth0ClientSecret,

                // Save the tokens to claims
                SaveIdToken = true,
                SaveAccessToken = true,
                SaveRefreshToken = true,

                // uncommenting this line fixes the 404 but User.Identity.isAuthenticared = FALSE
                //CallbackPath = new PathString("/bp"),

                // If you want to request an access_token to pass to an API, then replace the audience below to 
                // pass your API Identifier instead of the /userinfo endpoint
                Provider = new Auth0AuthenticationProvider()
                {
                    OnApplyRedirect = context =>
                    {
                        string userInfoAudience = $"https://{auth0Domain}/userinfo";
                        string redirectUri = context.RedirectUri + "&audience=" + WebUtility.UrlEncode(userInfoAudience);

                        context.Response.Redirect(redirectUri);
                    },
                    OnAuthenticated = context =>
                    {
                        // Get the user's roles
                        var rolesObject = context.User["https://schemas.quickstarts.com/roles"];
                        if (rolesObject != null)
                        {
                            string[] roles = rolesObject.ToObject<string[]>();
                            foreach (var role in roles)
                            {
                                context.Identity.AddClaim(new Claim(ClaimTypes.Role, role, ClaimValueTypes.String, context.Connection));
                            }
                        }


                        return Task.FromResult(0);
                    }
                }
            };
            options.Scope.Add("email"); // Request user's email address as well

            app.UseAuth0Authentication(options);
        }
    }
}

I do not see anything strange in your code. It should work.

At what point do you check User.Identity.IsAuthenticated ?

Is it possible you can give me a more complete project which demonstrates the problem end-to-end?

That’s exactly my thought - it should work.

User.Identity.IsAuthenticated is checked in an area Layout view, which is nested into the main website’s Layout. Which makes me ponder if nested views could be an issue? Surely not.

I will try to create a minimal project which demonstrates the issue but this isn’t a trivial task. This is a large project (5Gb). It’ll require a lot of gutting to remove all the unrelated stuff. I guess this is my last shot as I’ve been trying to fix this for several days now.

Actually, I made a copy of the project with the endeavour of reducing it to a minimum, and without making any modifications, when loading any page of the controller that contains the Auth0 authorization, I get a different error! I get redirected to the website root with the following URL error: /?error=access_denied

I can’t find any documentation on this behaviour. Any idea why this occurs? This is before/instead of any login attempt.

Many thanks.

I have no idea. My initial thoughts was that it may be because your host it on a virtual site (I saw the /bp/ path and assumed it was a virtual site), but I could not replicate that. I tested our quickstart on a virtual site and it works fine.

My next thought was that you were checking the .IsAuthenticated property before the authentication flow was completed, which why I was asking about where you check that property. But it does not sound like that is the issue though…

It may be something related to the areas you are using, but I am not sure.

Unfortunately, unless I can get something to somehow replicate that behaviour on my side, it will be extremely difficult to track this down… :frowning_face:

Unfortunately, unless I can get something to somehow replicate that behaviour on my side, it will be extremely difficult to track this down… :frowning_face:

I understand. I’ll try to get a stripped down project working if I can get around the /?error=access_denied redirect issue. Any idea why a copy of the original project would behave like this?!

No, sorry, I have not seen this before :frowning:

The other possibility was that the cookie was not saved, but you are using the Kentor middleware which works around that particular issue. So I am a little bit stumped.

OK. Going back to the initial issue… where is the login callback /signin-auth0 defined? And can this be safely changed without breaking the login flow?

Also, what is the name of the cookie that should be set after a successful login? The only cookie I see getting set in the test project that works is a session cookie called .AspNet.Cookies

Thank you for your time looking into this. We already committed to using Auth0 in a couple of our projects and I have to get it working somehow!

Yes, that is the name of the cookie which must be set, e.g

If that cookie is set, it means the authentication flow is completed and the user should be signed in (the IsAuthenticated property should be true).

As for the /signin-auth0 callback path, that is what we default to, but you can override it to whatever you want, e.g.

var options = new Auth0AuthenticationOptions()
{
    Domain = auth0Domain,
    ClientId = auth0ClientId,
    ClientSecret = auth0ClientSecret,

    CallbackPath = new PathString("/a/b/c/whatever"),

    // ....
};
app.UseAuth0Authentication(options);

If you do this, however, you will also need to configure your application in Auth0 to use that callback URL:

I am still a bit unsure however as to why this is happening.

It may also be worth your while to ask on the ASP.NET forums. The Auth0 middleware is just a normal piece of OWIN authentication middleware, so this issue may not be related to Auth0 specifically, but to something else with regards to the OWIN middleware.

One thing I am unsure of is where the /bp path comes from in your app? Is that the path of your area? If so then I am thinking that maybe when you set the CallbackPath to a path also starting with /bp/..., that maybe the area tries to resolve that URL and that is why you initially had the 404 when you set CallbackPath = new PathString("/bp")

None of this however explains why it does not work when you leave the CallbackPath as the default value. It should work… That is why I need to see a full sample app, otherwise it will be very difficult to track down the issue…

I just double checked and that cookie doesn’t get set in our project, even after it shows as login successful in Auth0 logs. Does this indicate that the Auth0 middleware isn’t picking up and setting User.Identity after a successful login? Is there anyway that I can try to debug why this is happening?

I thought that perhaps this was the hardwired into the Auth0 library to process the login and set the cookie and that changing it might break the flow. Good to get the confirmation that it doesn’t.

Yes, I am aware of that thank you. Auth0 is pretty good at displaying an error message when paths aren’t allowed. It’s the silent failing that’s a nightmare to debug!

Thanks, I’ll post on their forums also.

Bp stands for business partners. The path was originally /business-partners but I changed it to /bp in case the hyphen was causing the path to be misunderstood somehow!

This is the area of the website that we want to hide behind the Auth0 login. It’s a kind of user area that allows setting of user preferences and posting product orders.

The 404 occurs only with the default callback /signin-auth0. It redirects fine when the CallbackPath is set to a custom PathString.

It’s mind boggling, why a copy of the project would redirect to the website root before any attempt at login! I’m lost as to what to try next. :frowning:

@jerrie1 I found the problem. :grinning:

We are using the URL rewrite module to, among other things, append trailing slashes to URLs without them.

For some reason /signin-auth0 works but /signin-auth0/ returns a 404!!

I’ll paste the URL rewrite rules we’re using if you want to try replicate the problem. I can’t believe I’ve been stumped for days because of a trailing slash!!! So glad to have finally spotted the it. I can finally sleep.

<rewrite>
  <!-- URL REWRITE NOTES: 
    1. URL rewrite rules apply to all files on the server, not just URL
    2. Beware the the Rewrite rules apply only to the relative URL path (not the domain such as http://www.localhost.test )
    3. Do not use uderscores _ in the URL as this is used as a flag character during the rewrite process below
  -->
  <rules>
    <!-- Don't run on stage03 -->
    <rule name="Blacklist chqstage03" stopProcessing="true">
      <match url="(.*)" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{HTTP_HOST}" pattern="chqstage03" />
      </conditions>
      <action type="None" />
    </rule>
    <!-- Ignore the "/assets/" folder so fonts & JS don't get fiddled with 
    (we may have to do this for other folders such as images and styles if we find issues) -->
    <rule name="Blacklist /assets folder" stopProcessing="true">
      <match url="^assets/" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
      <action type="None" />
    </rule>
    <!-- Append a tralling slash if missing and if the request is not for a file or directory -->
    <!-- WARNING: this breaks Auth0 login -->
    <!--<rule name="Add trailing slash" stopProcessing="false">
      <match url="(.*[^/])$" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{HTTP_METHOD}" pattern="GET" />
        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
      </conditions>
      <action type="Rewrite" url="_{R:1}/" />
    </rule>-->
    <!-- Make URL all lower case for GET requests only (ignore any form Posts or AJAX POST calls) -->
    <rule name="Force lower case" stopProcessing="false">
      <match url="(.*)" ignoreCase="false" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{HTTP_METHOD}" pattern="GET" />
        <add input="{R:1}" pattern="[A-Z]" ignoreCase="false" />
      </conditions>
      <action type="Rewrite" url="_{ToLower:{R:1}}" />
    </rule>
    <!-- Add www. to domain name (which is stored in the HTTP_HOST server variable) so that it can be used in subsequent redirect -->
    <rule name="Pre-append www to domain" enabled="true" stopProcessing="false">
      <match url="^(.*)$" />
      <conditions trackAllCaptures="true">
        <add input="{HTTP_HOST}" pattern="^(?!www\.)(.*)$" />
      </conditions>
      <serverVariables>
        <set name="HTTP_HOST" value="www.{C:0}" />
      </serverVariables>
      <action type="Rewrite" url="_{R:1}" />
    </rule>
    <!-- Redirect if URL path starts with _ (meaning it was changed above) -->
    <rule name="Redirect if URL rewritten" stopProcessing="true">
      <match url="^(_+)(.*)" />
      <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
        <add input="{HTTP_METHOD}" pattern="GET" />
      </conditions>
      <action type="Redirect" url="https://{HTTP_HOST}/{R:2}" />
    </rule>
  </rules>
</rewrite>

Great! Happy to hear this one is solved. It was a bit of a strange one :wink:

Thanks!

I’m not sure if you’re in touch with the devs. If you are do you have any idea why /signin-auth0 works but /signin-auth0/ doesn’t work? It looks like an oversight as it is a valid URL.

@mitWeb Apologies for the delayed response, I only had time to properly investigate this today.

I was not able to get all you rewrite rules working, as I am running IIS Express, but I did get it working with the relevant rule that appended the trailing slash to the url.

What I had to do was to also add a trailing slash to the CallbackPath property when registering the Auth0 middleware, e.g.

var options = new Auth0AuthenticationOptions()
{
    CallbackPath = new PathString("/signin-auth0/"),
    Domain = auth0Domain,
    ClientId = auth0ClientId,
    ClientSecret = auth0ClientSecret,
    ErrorRedirectPath = new PathString("/Account/LoginError"),

    Provider = provider
};
app.UseAuth0Authentication(options);

And then you also need to update the Allowed Callback URL for your application in the Auth0 Dashboard to include the trailing slash, e.g.

image

With all of this in place, I got the authentication flow to complete successfully