0

(I posted this same message on the Auth0 Forum, but I'm asking here too in case it is not an authentication issue.)

I have a .NET Core application that uses Auth0's Authentication API to authenticate Active Directory users. All of that works as expected. When I run the app from Visual Studio 2015 and open it in my browser, it seems to remember my previous login and does not redirect me to sign in again for a while (the valid length of the JWT, perhaps?).

When the app is published to IIS on Windows Server 2012, users are signed out after about 20 minutes of inactivity.

My research so far has led me to articles about changing the settings of the App Pool in IIS to allow longer sessions. I can't help but think that maybe there is a better way, by re-checking the cookie or something. I would be happy to post the code from my Startup.cs file or whatever else might be helpful.

Is it possible to re-authenticate users automatically using the OIDC/cookie middleware rather than extending the IIS session time? If the best practice is to extend the session, feel free to tell me that too.

Any tips to point me in the right direction would be greatly appreciated. Thanks!

Update (Code Snippets)

Here's some code to look at! I need another pair of eyes on it.

Here is my ConfigureServices method in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRouting(options => options.LowercaseUrls = true);

    // Add authentication services
    services.AddAuthentication(
        options => options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);

    services.AddScoped<IMyRepository, MyRepository>();

    // Add framework services.
    services.AddMvc(config =>
    {
        var policy = new AuthorizationPolicyBuilder()
                        .RequireAuthenticatedUser()
                        .Build();
        config.Filters.Add(new AuthorizeFilter(policy));

        if (_env.IsProduction())
        {
            config.Filters.Add(new RequireHttpsAttribute());
        }
    });

    // Add functionality to inject IOptions<T>
    services.AddOptions();

    // Add the Auth0 Settings object so it can be injected
    services.Configure<Auth0Settings>(Configuration.GetSection("Auth0"));
}

And here is my Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Auth0Settings> auth0Settings)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    // Add the cookie middleware
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AutomaticAuthenticate = true,
        AutomaticChallenge = true
    });

    // Add the OIDC middleware
    var options = new OpenIdConnectOptions("Auth0")
    {
        // Set the authority to our Auth0 domain
        Authority = $"https://{auth0Settings.Value.Domain}",

        // Configure the Auth0 Client ID and Client Secret
        ClientId = auth0Settings.Value.ClientId,
        ClientSecret = auth0Settings.Value.ClientSecret,

        // Do not automatically authenticate and challenge
        AutomaticAuthenticate = false,
        AutomaticChallenge = false,

        // Set response type to code
        ResponseType = "code",

        // Set the callback path, so Auth0 will call back to http://localhost:5000/signin-auth0
        // Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
        CallbackPath = new PathString("/signin-auth0"),

        // Configure the Claims issuer to be Auth0
        ClaimsIssuer = "Auth0",

        Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                if (context.Properties.Items.ContainsKey("connection"))
                    context.ProtocolMessage.SetParameter("connection", context.Properties.Items["connection"]);

                return Task.FromResult(0);
            }
        }
    };
    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("name");
    options.Scope.Add("email");
    options.Scope.Add("groups");
    options.Scope.Add("roles");
    app.UseOpenIdConnectAuthentication(options);

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Customer}/{action=Index}/{id?}");
    });
}

For good measure, this is my Login method in my account controller:

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel vm, string returnUrl = null)
{
    if (ModelState.IsValid)
    {
        try
        {
            AuthenticationApiClient client = new AuthenticationApiClient(new Uri($"https://{_auth0Settings.Domain}"));

            var result = await client.AuthenticateAsync(new AuthenticationRequest
            {
                ClientId = _auth0Settings.ClientId,
                Scope = "openid name email groups",
                Connection = "<trimmed>",
                Username = vm.Username,
                Password = vm.Password
            });

            // Get user info from token
            var user = await client.GetTokenInfoAsync(result.IdToken);

            // Create claims principal
            var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, user.UserId),
                new Claim(ClaimTypes.Name, user.FullName),
                new Claim(ClaimTypes.Email, user.Email)
            }, CookieAuthenticationDefaults.AuthenticationScheme));

            // Sign user in to cookie middleware **LOOK**
            await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);

            return RedirectToLocal(returnUrl);
        }
        catch (ApiException ex)
        {
            // trimmed...
        }
        catch (Exception ex)
        {
            // trimmed...
        }
    }
    return View(vm);
}
wags
  • 149
  • 12
  • Do you still have outstanding questions or the replies in Auth0 forum were enough? – João Angelo Nov 25 '16 at 11:03
  • @JoãoAngelo I am still having trouble getting it to work. That Auth0 thread has more information, but no good answer yet. The app will respect a _shorter_ expiration time that I set, but not longer. – wags Nov 29 '16 at 14:13
  • Do you use anything that relies on some kind of in memory storage that could lead to the users being logged out if IIS recycles the application pool? – João Angelo Nov 29 '16 at 15:17
  • It's possible, but definitely not intentionally. When I recycle the app pool, everyone has to sign in again. When the app pool session expires, a user has to sign in again. My hunch is that I am still missing something in my Auth0/cookie middleware configuration. I'll add some code to the original question to help clarify things. – wags Nov 30 '16 at 18:22

1 Answers1

1

I was able to get this to work after running the .NET Core provisioning script on my web server. (A big shoutout to @pinpoint over on the .NET Core Slack channel for helping me figure this out!)

Implementing better logging on my server helped confirm the issue very quickly. I had good success with Serilog and the Serilog.Sinks.RollingFile package.

The authenticated cookies persist properly now, and even survive app pool recycles.

wags
  • 149
  • 12