aspnet / Security

[Archived] Middleware for security and authorization of web apps. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
1.27k stars 599 forks source link

[Draft] Auth 2.0 Migration announcement #1310

Closed HaoK closed 7 years ago

HaoK commented 7 years ago

Note: this issue is closed, you should use https://github.com/aspnet/Security/issues/1338

Summary:

The old 1.0 Authentication stack no longer will work, and is obsolete in 2.0. All authentication related functionality must be migrated to the 2.0 stack, any interop between old and new must be side by side apps, as opposed to mixing 1.0 auth code with 2.0 auth code in the same app. Cookie authentication will interop, so 1.0 Cookies and 2.0 Cookies will be valid in both apps if configured properly. The main motivation was to move to a more flexible service based IAuthenticationService and away from the old middleware/IAuthenticationManager design that came over from Microsoft.Owin.

IAuthenticationManager(aka httpContext.Authentication) is now obsolete

This was the main entry point into the old auth system. This has now been replaced with a new set of HttpContext extensions that live in the Microsoft.AspNetCore.Authentication namespace and remain very similar:

// Add using to pickup the new extension methods
using Microsoft.AspNetCore.Authentication;

// Update by just removing the .Authentication
context.Authentication.AuthenticateAsync => context.AuthenticateAsync
context.Authentication.ChallengeAsync => context.ChallengeAsync

Configure(): UseXyzAuthentication has been replaced by ConfigureService(): AddXyz()

In Auth 1.0, every auth scheme had its own middleware, and startup looked something like this:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) {
    app.UseIdentity();
    app.UseCookieAuthentication(new CookieAuthenticationOptions
       { LoginPath = new PathString("/login") });
    app.UseFacebookAuthentication(new FacebookOptions
       { AppId = Configuration["facebook:appid"],  AppSecret = Configuration["facebook:appsecret"] });
} 

In Auth 2.0, there is now only a single Authentication middleware, and each authentication scheme is registered during ConfigureServices, and UseIdentity() is no longer required (since it was just calling UseCookie 4 times underneath the covers)

public void ConfigureServices(IServiceCollection services) {
    services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores();
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(o => o.LoginPath = new PathString("/login"))
                .AddFacebook(o =>
                {
                    o.AppId = Configuration["facebook:appid"];
                    o.AppSecret = Configuration["facebook:appsecret"];
                });
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) {
    app.UseAuthentication();
}

New Microsoft.AspNetCore.Authentication.Core/Abstractions

All of the old Authentication namespaces in HttpAbstractions have been deprecated. The new Auth 2.0 stack lives in two new packages inside the HttpAbstractions repo: Microsoft.AspNetCore.Authentication.Core/Abstractions.

Brief overview:

Types that are mostly unchanged, just with new homes:

Security repo: Microsoft.AspNetCore.Authentication / AuthenticationHandler changes

All of the core abstractions and services for authentication live in HttpAbstracions, but there's an additional layer of base classes/functionality targeted towards implementation of AuthenticationHandlers. This is also where the AuthenticationMiddleware lives. The handlers themselves for the various implementations aren't drastically different, but there were a fair amount of changes

Microsoft.AspNetCore.Authentication

Overview:

Event changes overview

At a high level, there 3 main kinds of events:

  1. BaseContext events which are the simplest and just expose properties with no real control flow.

  2. ResultContext events which revolve around producing AuthenticateResults which expose:

    • Success(): used to indicate that authentication was successful and to use the Principal/Properties in the event to construct the result.
    • NoResult(): used to indicate no authentication result is to be returned.
    • Fail(): used to return a failure.
  3. HandleRequestContext events are used in the IAuthenticationRequestHandler/HandleRemoteAuthenticate methods and adds two more methods:

AutomaticAuthentication/Challenge have been replaced by Default[Authenticate/Challenge]Scheme

AutomaticAuthentication/Challenge were intended to only be set on one authentication scheme, but there was no good way to enforce this in 1.0. These have been removed as flags on the individual AuthenticationOptions, and have been moved into the base AuthenticationOptions which can be configured in the call to AddAuthentication(authenticationOptions => authenticationOptions.DefaultScheme = "Cookies").

There are now overloads that use the default schemes for each method in IAuthenticationService

"Windows" Authentication(HttpSys/IISIntegration)

The host behavior hasn't changed too much, but now they each register a single "Windows" authentication scheme. Also IISIntegration now conditionally registers the handler only if windows auth is enabled in IIS (if you have the latest version of ANCM, otherwise it's always registered as before).

Authorization changes

IAuthorizationService.AuthorizeAsync now returns AuthorizationResult instead of bool

In order to enable scenarios around authorization failures, IAuthorizationService now returns a result object which allows access to the reasons why AuthorizeAsync failed (either context.Fail(), or a list of failed requirements)

Removal of ChallengeBehavior => new PolicyEvaluator

In Auth 1.0, there was a ChallengeBehavior enum that was used to specify either Automatic/Unauthorized/Forbid behaviors to signal to the auth middleware what behavior the caller wanted. Automatic was the default and would go down the Forbid(403) code path if the middleware already had an authentication ticket, otherwise would result in Unauthorized(401).

In Auth 2.0, this behavior has been moved into a new Authorization.Policy package, which introduces the IPolicyEvaluator which uses both IAuthenticationService (when requested via policy.AuthenticationSchemes), and IAuthorizationService to decide whether to return a tri state PolicyAuthorizationResult (Succeeded/Challenged/Forbidden).

Overview of [Authorize]

The [Authorize] attribute hasn't changed much, but the there were some implementation details that have changed significantly in MVC's AuthorizeFilter, and here's an overview of how things work: AuthorizeFilter source

  1. An effective policy is computed by combining all of the requested policies/requirements from all relevant [Authorize] attributes on the controller/method/globally.
  2. IPolicyEvaluator.AuthenticateAsync(policy, httpContext) is called, by default, if the has specified any policy.AuthenticationSchemes, AuthenticateAsync will be called on each scheme, and each resulting ClaimsPrincipal will be merged together into a single ClaimsPrincipal set on context.User. If no schemes were specified, the evaluator will attempt to use context.User if it contains an authenticated user. This is usually the normal code path, as DefaultScheme/DefaultAuthenticateScheme will be set to the main application cookie, and the AuthenticationMiddleware will have already set context.User using this scheme's AuthenticateAsync() Authenticate logic
  3. If AllowAnoynmous was specified, authorization is skipped, and the filter logic short circuits and is done.
  4. Finally, IPolicyEvaluator.AuthenticateAsync(policy, authenticationResult, httpContext) is called with the result from step 2. This just basically turns into a call to IAuthorizationService.AuthorizeAsync, and the result is used to determine the appropriate Challenge/ForbidResult if needed.

Claims Transformation

Simpler, new IClaimsTransformation service with a single method: Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)

We call this on any successful AuthenticateAsync call.

        services.AddSingleton<IClaimsTransformation, ClaimsTransformer>();

        private class ClaimsTransformer : IClaimsTransformation {
            // Can consume services from DI as needed, including scoped DbContexts
            public ClaimsTransformer(IHttpContextAccessor httpAccessor) { }
            public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal p) {
                p.AddIdentity(new ClaimsIdentity());
                return Task.FromResult(p);
            }
        }

Known issues/breaking changes:

HaoK commented 7 years ago

I'm drafting the announcement here, when we are all happy, I'll open the real announcements issue

cc @Rick-Anderson @Tratcher @blowdart

Tratcher commented 7 years ago

Katana => Microsoft.Owin.

In 1.0 didn't UseIdentity() need to be before Facebook?

Your layout for the 2.0 sample needs work.

Is this what the fwlink will point to in the short term?

[Authorize] also relies on DefaultAuthenticateScheme.

Missing a new line on DefaultForbidScheme.

Doesn't DefaultSignOutScheme fall back to SignInScheme? Same for Forbid?

cwe1ss commented 7 years ago
tylerd commented 7 years ago

HttpContext.Authentication.GetAuthenticateInfoAsync(scheme) was used to obtain Auth tokens to use with external services, like Fitbit or GitHub.

How is this accomplished in 2.0?

Example: AspNetCore - Get current access token

Also: The warning message when using HttpContext.Authentication is incorrect

Warning CS0618  'HttpContext.Authentication' is obsolete: 'This is obsolete and will be removed in a future version. The recommended alternative is to use Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions. See https://go.microsoft.com/fwlink/?linkid=845470.

The link just takes you to the aspnet core starting page, and I cannot find Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions anywhere.

Tratcher commented 7 years ago

@tylerd try var result = await HttpContext.GetTokenAsync(scheme, "access_token");

Tratcher commented 7 years ago

https://github.com/aspnet/HttpAbstractions/blob/271faf11bbd5b05cd758f1c7e83eb59d45b6db59/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationHttpContextExtensions.cs

HaoK commented 7 years ago

updated with feedback, not sure what layout issue @Tratcher feel free to fix it directly.

Added a placeholder to go over AuthZ changes as well, Authorize deserves a section even tho it hasn't changed that much.

Tratcher commented 7 years ago

Updated the indenting

HaoK commented 7 years ago

Added a major section on Authorization/Authorize/ChallengeBehavior

kevinchalet commented 7 years ago

It looks good, but you should maybe insist on the fact the old stack was not just obsoleted but is now completely unusable. It would be also great to mention that older middleware written for 1.0 will not work and that moving to a 2.0.0-compatible version is mandatory.

To use a more advanced scoped claims transform that uses DbContext's, they would just need to add their own IClaimsTransformation before any AddAuthentication calls.

What, did you really rename IClaimsTransformer to IClaimsTransformation? What was the reasoning behind this change?

HaoK commented 7 years ago

@Tratcher can you write the section on host changes for HttpSys/IISIntegration in a comment and I'll merge it?

HaoK commented 7 years ago

@PinpointTownes they don't look the same for one, and I prefer the new name, https://github.com/aspnet/Security/blob/rel/1.1.2/src/Microsoft.AspNetCore.Authentication/IClaimsTransformer.cs vs https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNetCore.Authentication.Abstractions/IClaimsTransformation.cs

kevinchalet commented 7 years ago

Buh, the new name is ugly :trollface:

Note that getting rid of ClaimsTransformationContext will prevent one from accessing the HTTP context... at least, without having to use IHttpContextAccessor. Maybe you could add a short line mentioned that as a workaround?

kevinchalet commented 7 years ago

Other things you should treat:

HaoK commented 7 years ago

@PinpointTownes well, its just a normal service now which can consume whatever it wants from DI, unlike before, but sure I'll call that out in the claims xform section.

Added some text about mixing 1.0 and 2.0 together in the initial section, but this probably needs a longer dedicated section in the known breaking changes when I get to it.

re Properties and things moving around, that will go into this placeholder section: Overview of Auth core/scheme provider/request handler section.

This announcment is probably going to be really really long (too long I think). Hopefully @Rick-Anderson will turn this into real docs :)

Tratcher commented 7 years ago

ChallengeBehavior.Automatic description is backwards, it forbids if you already have a ticket.

Tratcher commented 7 years ago

Not much to say for the hosts. The biggest visible differences are that they were each consolidated to a single "Windows" scheme, and that IISIntegration now conditionally registers the handler only if windows auth is enabled in IIS (if you have the latest version of ANCM, otherwise it's always registered as before).

HaoK commented 7 years ago

Cool thanks, added the host section

HaoK commented 7 years ago

Added "New Microsoft.AspNet.Authentication.Core/Abstractions" section

Tratcher commented 7 years ago

Needs a sample showing this common scenario:

services.AddAuthentication(sharedOptions =>
{
  sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
HaoK commented 7 years ago

@danroth27 didn't you say that you were able to just always challenge cookie? Or is your scenario different?

HaoK commented 7 years ago

Its also there in the example already: AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) but I'll call more attention to this in the section I'm writing now which will go over the AuthenticationMiddleware in more detail

Tratcher commented 7 years ago

Dan's scenario is different, he has a login page. For OAuth/OIDC apps without a login page then you need to specify the default challenge too.

HaoK commented 7 years ago

Updated doc with Security/Authenticationhandler overviews along with a short overview of the context/events methods.

HaoK commented 7 years ago

Bah github fail, lost the update, was in preview, and commenting nukes the edits :(

HaoK commented 7 years ago

Ok updated again, fun fact: so turns out you can recover form posts that were lost after a submit by taking a process dump of a chrome tab... good trick if you ever lose a HUGE post :)

HaoK commented 7 years ago

Removed dedicated cookie interop section, since the initial paragraph covered it.

Looks good to convert to announcments @Tratcher?

HaoK commented 7 years ago

@danroth27 this look reasonable to you for the announcement?

Tratcher commented 7 years ago

:shipit:

HaoK commented 7 years ago

https://github.com/aspnet/Announcements/issues/262

scottaddie commented 7 years ago

@HaoK @Tratcher I'll need a couple edits to this issue to help me finish the migration doc:

chikien276 commented 7 years ago

Sir, what happened to this peace of code?

services.Configure<IdentityOptions>(option => { config.Cookies... }

HaoK commented 7 years ago

You need to configure the cookies by their name instead, you can use https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity/IdentityServiceCollectionExtensions.cs#L39

services.ConfigureApplicationCookie()

rheid commented 7 years ago

@HaoK , @Tratcher:

After successful done a AzureAdAuthentication. I want query more Office 365 API with id_token and want to get more access tokens via and query SharePoint Online:

What is the best way?

  1. Get the access token for SharePoint /oauth2/authorize?client_id=...
  2. Query SharePoint Online /_api/web/lists('9d37ec3f-8f24-41df-9427-e79945954e9f')

What I this best way to make this querys and how I can ID_Token:

How I get the id_token?

This return NULL and the old way not work anymore with 2.0 var identityToken = await HttpContext.GetTokenAsync("id_token");

OLD Query for idToken get the saved identity token var identityToken = await HttpContext.Authentication .GetTokenAsync(OpenIdConnectParameterNames.IdToken);

Authentication Options with id_token public void PostConfigure(string name, OpenIdConnectOptions options) { options.ClientId = _azureOptions.ClientId; options.ClientSecret = _azureOptions.ClientSecret; options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}"; options.UseTokenLifetime = true; options.CallbackPath = _azureOptions.CallbackPath; options.RequireHttpsMetadata = false; options.GetClaimsFromUserInfoEndpoint = true; options.ResponseType = "code id_token"; }

chris31389 commented 7 years ago

Is there any examples where you don't use cookies within the AddAuthentication method? I was hoping to convert this to the new services.AddAuthentication(...) method:

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
   Audience = "an.app-url.com",
   Authority = "identity.example.com",
   AutomaticAuthenticate = true
});
kevinchalet commented 7 years ago

@chris31389 something like that?


public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        })

        .AddJwtBearer(options =>
        {
            options.Authority = "http://localhost:30940/";
            options.Audience = "resource-server";
            options.RequireHttpsMetadata = false;
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseAuthentication();

        // ...
    }
}
chris31389 commented 7 years ago

@PinpointTownes Awesome, thank you 🏆

KarinNooteboom commented 7 years ago

Please help using AspnetCore 2.0 ( released today ) VS2017 15.3 in ConfigureServices I have:

services.AddAuthentication(o => {
                o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
             .AddCookie(options =>
             {
                 options.LoginPath = new PathString("/Account/Login");
                 options.LogoutPath = new PathString("/Account/Logout");
             });

In Configure I have app.UseAuthentication();

I have a seperate project which produces a result with the claims principal filled with claims like :

  var claims = new List<Claim>
     {
                        new Claim(ClaimType.UserId.ToString(), user.UserId.ToString(), ClaimValueTypes.Integer),
                        new Claim(ClaimType.OrganisationId.ToString(), user.OrganisationId.ToString(), ClaimValueTypes.Integer),
                        new Claim(ClaimType.CultureInfoSetting.ToString(), user.CultureCode, ClaimValueTypes.String),
                        new Claim(ClaimType.SysRoleLevel.ToString(), user.SysRoleLevel.ToString(), ClaimValueTypes.Integer),
                        new Claim(ClaimType.Theme.ToString(), user.Theme, ClaimValueTypes.String)
                    };
                    var identity = new ClaimsIdentity(claims, "Cookies");
                    var p = new ClaimsPrincipal(identity);
                    result.ClaimsPrincipal = p;

And in my AccountController LogIn action to SignIn : await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, signin.ClaimsPrincipal, new AuthenticationProperties { IsPersistent = true });

However the HttpContext.User is not set, is not authenticated and my code bounces me back to the Login page as i have the Authorize attribute in Home/Index .
What am I doing wrong ???

HaoK commented 7 years ago

Double check that your sign in call is getting hit in the debugger, if so, make sure you see the cookie being sent (via F12 in your brower), if all of that looks fine, look at your logs and see if you see any Cookie Authentication errors that talk about failed to decrypt cookie or the like. Make sure your UseAuthentication is before UseMvc

KarinNooteboom commented 7 years ago

Thanks HaoK, but the problem seems to be the following ( cause when I removed it, now authentication works ) In ConfigureServices I had localisation => services.Configure(options => { var supportedCultures = new List { new CultureInfo("en-US"), new CultureInfo("nl-NL"), new CultureInfo("en-GB"), new CultureInfo("de-DE"), new CultureInfo("fr-FR"), new CultureInfo("es-ES"), new CultureInfo("sv-SE") }; options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US"); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures; options.RequestCultureProviders.Insert(0, new PossOnline.Services.Localisation.CustomCultureProvider()); });

In Configure => var locOptions = app.ApplicationServices.GetService<IOptions>(); app.UseRequestLocalization(locOptions.Value);

And in my AccountController after the Signin prior to the redirect to Home/Index => Response.Cookies.Append( CookieRequestCultureProvider.DefaultCookieName, CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(result.CultureCode, result.CultureCode)), new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) } ); When I remove this, authentication works..

HaoK commented 7 years ago

I'd just use F12 to look at the delta in what cookies its sending with/without that change, that should give you a hint with whats going on

vbornand commented 7 years ago

In the Claims Transformation sample, there is the comment: // Can consume services from DI as needed, including scoped DbContexts

Is it really safe to use a scoped service in a singleton service?

In my opinon, we have to use a ServiceProvider (for example http://morgwai.pl/articles/aspdi.html)

davidfowl commented 7 years ago

Is it really safe to use a scoped service in a singleton service?

No, this actually throws by default in 2.0.

maxlego commented 7 years ago

Works almost nicely.

But when i try to chagange login path like this:

            services
                .AddAuthentication(o =>
                {
                    o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                })
                .AddCookie(o =>
                {
                    o.LoginPath = "/login";
                    o.LogoutPath = "/logout";
                });

and in Configure method i use

            app.UseAuthentication();

it will still redirect me to /account/login

What am i doing wrong?

davidfowl commented 7 years ago

This can be simplified to:

services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(o =>
    {
        o.LoginPath = "/login";
        o.LogoutPath = "/logout";
    });

It should also work. Not sure why it's not using the specified options. What other code do you have in the application

maxlego commented 7 years ago

my bad.

It seems that problem was that i was using Castle.Windsor.MsDependencyInjection. Not sure why though...

cgountanis commented 7 years ago

Something people have been confused about is actually using custom cookie based auth. This helped me: https://stackoverflow.com/questions/43961571/asp-net-core-2-0-preview-1-how-to-set-up-cookie-authentication-with-custom-logi

AndyCreigh commented 7 years ago

Hi, My cookie authentication without identity works on the dev server but not debugging against localhost in Chrome. I see the cookie getting created but keep getting re-directed back to the login page. Code below, did I miss something?

A walk through in core 2 would be a great help - https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore1x - could do with a troubleshooting example.

Thanks.

ConfigureServices

        // Set up cookie based authorisation
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options =>
            {
                options.LoginPath = new PathString("/Account/Login/");
                options.LogoutPath = new PathString("/Account/Logoff/");
                options.AccessDeniedPath = new PathString("/Account/AccessDenied/");
            });

        // Add framework services.
        services.AddMvc();

Configure

        app.UseAuthentication();

AccountController

    [HttpGet]
    public async Task<IActionResult> Login(string returnUrl = null)
    {
        // Clear the existing external cookie to ensure a clean login process
        //await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

        ViewData["ReturnUrl"] = returnUrl;
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;

        if (ModelState.IsValid)
        {
            // https://andrewlock.net/introduction-to-authentication-with-asp-net-core/
            // https://code.msdn.microsoft.com/ASPNET-Core-MVC-Authenticat-39b4b103
            // https://stackoverflow.com/questions/20254796/why-is-my-claimsidentity-isauthenticated-always-false-for-web-api-authorize-fil/30430228#30430228
            // https://msdn.microsoft.com/en-us/library/system.security.claims.authenticationtypes(v=vs.110).aspx
            // https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims

            var claimsIdentity = new ClaimsIdentity(null, CookieAuthenticationDefaults.AuthenticationScheme);
            ClaimsPrincipal principal = new ClaimsPrincipal(claimsIdentity);

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
            var isAuthenticated = principal.Identity.IsAuthenticated;

            //return LocalRedirect(returnUrl);
            if (returnUrl != null)
            {
                return Redirect(returnUrl);
            }
            else
            {
                return Redirect("~/Administrator/Menu");
            }
        }
        return View();
    }
PaulAngelino commented 7 years ago

Hi, I'm trying to adapt a custom implementation of Identity framework from pre .NET Core where have our own user manager, role manager and sign in manager. We do this because we have our own internal account store, and use SAML authentication. I need to redirect the user to our SAML authentication service. I have the following in my ConfigureServices() prior to AddMvc:

            services.AddIdentity<ApplicationUser, TNCRole<IdentityRole>>()
                .AddUserManager<ApplicationUserManager>()
                .AddRoleManager<ApplicationRoleManager>()
                .AddSignInManager<ApplicationSignInManager>();

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options =>
            {
                options.Cookie.Name = IdentityConfig.CookieName;
                options.Cookie.Domain = cookieDomain;
                options.ExpireTimeSpan = TimeSpan.FromMinutes(sessionDurationMinutes);
                options.SlidingExpiration = true;
                options.Events = new CookieAuthenticationEvents()
                {
                    OnValidatePrincipal = context =>
                    {
                        SecurityStampValidator.ValidatePrincipalAsync(context);
                        return Task.FromResult(0);
                    },
                    // This is needed because the LoginPath option only supports relative URL's.
                    OnRedirectToLogin = ctx =>
                    {
                        ctx.RedirectUri = _signInRedirectUri;
                        ctx.Response.Redirect(ctx.RedirectUri);
                        return Task.CompletedTask;
                    }
                };
            });

With all this in place, the redirect to the external authentication service never happens. Instead it tries to redirect to ~/Account/Login. If I take out the first statement (services.AddIdentity), the redirect to our SAML service happens properly, but then I'm not registering our custom user manager, role manager and sign in manager. Can you please help me understand what I need to do to wire this up properly?

rbasniak commented 7 years ago

I'm trying to convert my old authentication to .NET 2.0. I had the following code:

        app.UseJwtBearerAuthentication(new JwtBearerOptions
        {
            AutomaticAuthenticate = true,
            IncludeErrorDetails = true,
            Authority = "https://securetoken.google.com/xxxxx",
            TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = "https://securetoken.google.com/xxxxxx",
                ValidateAudience = true,
                ValidAudience = "xxxx",
                ValidateLifetime = true,
            },
        });

My new code is the following:

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationServices();

    services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(DatabaseContext.ConnectionString));

    services.AddCors();

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.IncludeErrorDetails = true;
            options.Authority = "https://securetoken.google.com/project-id";
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = "https://securetoken.google.com/project-id",
                ValidateAudience = true,
                ValidAudience = "project-id",
                ValidateLifetime = true,
            };
        });

    services.AddMvc().AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); });

    services.AddAuthorization(options =>
    {
        options.AddPolicy(Policies.CorporativeKeysManager, policy => policy.RequireRole(Roles.CorporativeKeysManager));
        options.AddPolicy(Policies.CorporativeNewsManager, policy => policy.RequireRole(Roles.CorporativeNewsManager));
        options.AddPolicy(Policies.CorporativeAdmin, policy => policy.RequireRole(Roles.CorporativeNewsManager, Roles.CorporativeKeysManager));
    });

    services.AddEntityFrameworkSqlServer();

    services.AddIdentity<ApplicationUser, IdentityRole>(config => { config.User.RequireUniqueEmail = true; config.Password.RequireNonAlphanumeric = false; })
        .AddEntityFrameworkStores<DatabaseContext>()
        .AddDefaultTokenProviders();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCors(c => c.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

    app.UseAuthentication();

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

    app.UseStaticFiles();
}

But in 2.0 I'm getting a 404 response. If I remove the [Authorize] attribute from my endpoint, it works. My output window shows this:

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:62423/api/users/info  
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed for user: (null).
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.Application was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action SORTE.API.ContentManager.Controllers.UsersController.Info (SORTE.API.ContentManager) in 24.0837ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 35.2446ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:62423/Account/Login?ReturnUrl=%2Fapi%2Fusers%2Finfo  
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 5.8149ms 404 

From the log errors, it seems that it's trying to redirect me to /Account/Login, but I don't have such endpoint, my project is Web API.

Am I missing some configuration?