Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

[Draft] Auth 2.0 Migration announcement #1310

Closed
HaoK opened this issue Jul 11, 2017 · 125 comments
Closed

[Draft] Auth 2.0 Migration announcement #1310

HaoK opened this issue Jul 11, 2017 · 125 comments
Assignees
Milestone

Comments

@HaoK
Copy link
Member

HaoK commented Jul 11, 2017

Note: this issue is closed, you should use #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:

  • IAuthenticationService: used by the HttpContext extension methods to expose the 5 main operations Authenticate/Challenge/Forbid/SignIn/SignOut
  • IAuthenticationHandler: Defines the required operations for all handlers: Authenticate/Challenge/Forbid
  • IAuthenticationSignIn/OutHandler: Implemented to add the SignIn/SignOut methods respectively
  • IAuthenticationRequestHandler: Implemented by handlers that need to participate in request handler, i.e. remote authentication schemes like OAuth/OIDC that need to process 3rd party auth responses.
  • AuthenticationScheme: represents a logical named authentication scheme to target for any given IAuthenticationService method, it binds the scheme name (and optional display name) to an IAuthenticationHandler which implements the scheme specific logic.
  • IAuthenticationSchemeProvider: responsible for managing which schemes are supported, and what the defaults are for each operation (the default implementation just reads from the AuthenticationOptions)
  • IAuthenticationHandlerProvider: responsible for returning the correct handler instance for a given scheme and request.
  • IAuthenticationFeature: used to capture the original request path/pathbase so redirects can be computed property after an app.Map()

Types that are mostly unchanged, just with new homes:

  • AuthenticationProperties: metadata for authentication operations.
  • AuthenticationTicket: used to store a claims principal (user) + authentication properties
  • AuthenticateResult: return value for AuthenticateAsync, contains either a ticket, or a failure

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:

  • AuthenticationMiddleware: UseAuthentication() adds this middleware which does two things. By default It will automatically Authenticate using AuthenticationOptions.DefaultAuthenticateScheme to set httpContext.User if specified. It also will give IAuthenticationRequestHandler's a chance to handle the request.
  • AuthenticationSchemeOptions: Base class for options used with the AuthenticationHandler base class, it defines two common properties in Events and ClaimsIssuer, as well as a virtual Validate() method that will be called on every request by the handler.
  • AuthenticationHandler<TOptions>: Abstract base class for handlers who must implement HandleAuthenticateAsync. The rest of IAuthenticationHandler is implemented with some reasonable defaults, Challenge(401)/Forbid(403), and logic to handle per request initialization using IOptionsMonitor<TOptions>.Get(authenticationScheme.Name) to resolve the handler's options.
  • RemoteAuthenticationHandler<TOptions>: Adds the abstract HandleRemoteAuthenticateAsync and implements HandleAuthenticateAsync to call this method. This is meant to be used for 3rd party authentication, i.e. OAuth/OIDC. It adds an additional constraint to TOptions that requires them to be RemoteAuthenticationOptions which adds a bunch of settings like CallbackPath, CorrelationCookie, Backchannel which are needed to talk to a remote authentication provider.
  • AuthenticationBuilder: is a new class which is used to group all of the AddXyz() authentication methods. This is returned by services.AddAuthentication() and is where specific authentication methods are expected to add themselves as extension methods, i.e. AddCookie(), AddGoogle(), AddFacebook().

Event changes overview

  • Thanks mostly to @PinpointTownes determination to improve the events story, we've refactored and renamed some things to improve the events experience (caveat: if you don't like the names we picked, don't blame @PinpointTownes we overruled him on naming)

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.
  1. HandleRequestContext events are used in the IAuthenticationRequestHandler/HandleRemoteAuthenticate methods and adds two more methods:
  • HandleResponse(): used to indicate that the response was generated and the AuthenticationMiddleware should not invoke the rest of the middlewares in the pipeline after it.

  • SkipHandler() used to indicate that this handler is done with the request, but subsequent handlers will be called, as well as any other middleware in the pipeline if none of those handlers handle the request.

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

  • DefaultScheme: if specified, all the other defaults will fallback to this value
  • DefaultAuthenticateScheme: if specified, AuthenticateAsync() will use this scheme, and also the
    AuthenticationMiddleware added by UseAuthentication() will use this scheme to set context.User automatically. (Corresponds to AutomaticAuthentication)
  • DefaultChallengeScheme if specified, ChallengeAsync() will use this scheme, [Authorize] with policies that don't specify schemes will also use this
  • DefaultSignInScheme is used by SignInAsync() and also by all of the remote auth schemes like Google/Facebook/OIDC/OAuth, typically this would be set to a cookie.
  • DefaultSignOutScheme is used by SignOutAsync() falls back to DefaultSignInScheme
  • DefaultForbidScheme is used by ForbidAsync(), falls back to DefaultChallengeScheme

"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:

  • In 1.0, it was possible to configure different Authentication middleware with branching, this is no longer possible with a single middleware and shared services across all branches. A workaround could be to use different schemes/options for each branch.
@HaoK HaoK self-assigned this Jul 11, 2017
@HaoK HaoK added this to the Discussion milestone Jul 11, 2017
@HaoK
Copy link
Member Author

HaoK commented Jul 11, 2017

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

cc @Rick-Anderson @Tratcher @blowdart

@HaoK HaoK changed the title Auth 2.0 Migration announcement WIP/Preview [Draft] Auth 2.0 Migration announcement Jul 11, 2017
@Tratcher
Copy link
Member

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
Copy link
Contributor

cwe1ss commented Jul 12, 2017

  • "... namespace are very similar" -> "... namespace and are very similar"?
  • In the first sample with extension methods you use "context.Authentication.AuthenticateAsync" twice instead of "ChallengeAsync"

@tylerd
Copy link

tylerd commented Jul 12, 2017

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
Copy link
Member

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

@HaoK
Copy link
Member Author

HaoK commented Jul 12, 2017

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
Copy link
Member

Updated the indenting

@HaoK
Copy link
Member Author

HaoK commented Jul 12, 2017

Added a major section on Authorization/Authorize/ChallengeBehavior

@kevinchalet
Copy link
Contributor

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
Copy link
Member Author

HaoK commented Jul 12, 2017

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

@kevinchalet
Copy link
Contributor

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
Copy link
Contributor

Other things you should treat:

  • AuthenticationProperties and its friends have moved to a different namespace. This change will affect folks who use it in their controllers and don't necessarily use the extensions.

  • Authentication middleware selection via branching is no longer possible (because there's now only one middleware). You should maybe suggest a workaround.

@HaoK
Copy link
Member Author

HaoK commented Jul 12, 2017

@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
Copy link
Member

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

@Tratcher
Copy link
Member

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
Copy link
Member Author

HaoK commented Jul 12, 2017

Cool thanks, added the host section

@HaoK
Copy link
Member Author

HaoK commented Jul 12, 2017

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

@Tratcher
Copy link
Member

Needs a sample showing this common scenario:

services.AddAuthentication(sharedOptions =>
{
  sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})

@HaoK
Copy link
Member Author

HaoK commented Jul 13, 2017

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

@HaoK
Copy link
Member Author

HaoK commented Jul 13, 2017

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
Copy link
Member

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
Copy link
Member Author

HaoK commented Jul 13, 2017

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

@davidfowl
Copy link
Member

davidfowl commented Aug 27, 2017

@irowbin

@davidfowl My custom action filter is to look for the permission from database. create, read, update, delete so i have to invoke custom action filter.

No, you don't. Policies support writing arbitrary logic. I recommend reading https://docs.microsoft.com/en-us/aspnet/core/security/authorization/

@coderabsolute
Copy link

@chikien276 I managed to get it working after a bit of struggling (after the post I made). Thanks for the code!

@irowbin
Copy link

irowbin commented Aug 28, 2017

@davidfowl You mean to get authenticated user without authorize attribute, I have to change like

var result = await context.AuthenticateAsync(scheme);

var hasClaim = result.Principal.Claims.Any(...);

instead of

context.HttpContext.User

is this correct?

@davidfowl
Copy link
Member

That's correct. I wrote an example here #1310 (comment)

@chrispaynter
Copy link

These docs in the below link are out of date, following them presents a deprecated warning when you use app.UseIdentity(). I can't seem to find any proper documentation on how to implement the new 2.0 identity?

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?tabs=visual-studio

@chrispaynter
Copy link

Ok I seem to have found some migration docs hidden away - https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x

@irowbin
Copy link

irowbin commented Sep 4, 2017

@davidfowl
i am getting succeeded authenticate on each refresh even my cookie is expired.

// add cookie
.AddCookie("scheme1", o =>
    {
            o.ExpireTimeSpan = TimeSpan.FromSeconds(30);
            o.LoginPath = new PathString("/FooUser");
            o.Cookie.Name = "token1";
            o.SlidingExpiration = true;
    })

var result = await HttpContext.AuthenticateAsync("scheme1");
if(result.Succeeded) // if i keep hitting F5 in every second, the authenticate is success.

but if i refresh after few second or may be after expire, it expired. Any idea?

@davidfowl
Copy link
Member

@irowbin
Copy link

irowbin commented Sep 4, 2017

@davidfowl Well, i have set expiration time to 30 second and SlidingExpiration = true; After cookie created, i am able to access user even my expiration limit is crossed. It happen only when i keep hitting refresh button between 1-2-10 second i guess, and never logged out. I don't understand.

@Tratcher
Copy link
Member

Tratcher commented Sep 4, 2017

The point of SlidingRefresh is to extend the cookie lifetime as long as the user remains active. When you press F5 the cookie gets renewed if it hasn't expired yet.

@irowbin
Copy link

irowbin commented Sep 4, 2017

@Tratcher Now i get it. Thanks

SvenEV added a commit to HiP-App/HiP-DataStore that referenced this issue Sep 7, 2017
parakhj added a commit to Azure-Samples/active-directory-b2c-dotnetcore-webapi that referenced this issue Sep 10, 2017
@ghost
Copy link

ghost commented Sep 15, 2017

In .NET Core 1.X, we had a JWT token authentication setup that included a TokenController responsible for generating tokens, TokenAuthenticationMiddleware that checks for valid tokens, and an ApiAuthorizeAttribute that we would use on specific controllers/methods to check for specific permissions.

In order to get it working with .NET Core 2.0, we did the following.

Updated Startup.cs:

.NET Core 1.X

            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("TokenProviderOptions:SecretKey").Value));

            var tokenValidationParameters = new TokenValidationParameters
            {
                RequireExpirationTime = true,
                RequireSignedTokens = true,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = Configuration.GetSection("TokenProviderOptions:Issuer").Value,
                ValidateAudience = true,
                ValidAudience = Configuration.GetSection("TokenProviderOptions:Audience").Value,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            };

            app.UseJwtBearerAuthentication(new JwtBearerOptions
            {
                Audience = Configuration.GetSection("TokenProviderOptions:Audience").Value,
                ClaimsIssuer = Configuration.GetSection("TokenProviderOptions:Issuer").Value,
                AutomaticAuthenticate = true,
                TokenValidationParameters = tokenValidationParameters,
                SaveToken = true
            });

to .NET Core 2.0

            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("TokenProviderOptions:SecretKey").Value));

            var tokenValidationParameters = new TokenValidationParameters
            {
                RequireExpirationTime = true,
                RequireSignedTokens = true,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = Configuration.GetSection("TokenProviderOptions:Issuer").Value,
                ValidateAudience = true,
                ValidAudience = Configuration.GetSection("TokenProviderOptions:Audience").Value,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            };

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.Audience = Configuration.GetSection("TokenProviderOptions:Audience").Value;
                options.ClaimsIssuer = Configuration.GetSection("TokenProviderOptions:Issuer").Value;
                options.TokenValidationParameters = tokenValidationParameters;
                options.SaveToken = true;
            });

Updated TokenAuthenticationMiddleware

.NET Core 1.X

        public async Task Invoke(HttpContext context, ITraffkTenantFinder traffkTenantFinder)
        {
            var authenticateInfo = await context.AuthenticateAsync("Bearer");
            var bearerTokenIdentity = authenticateInfo?.Principal;

            if (bearerTokenIdentity != null)
            {
                var currentTenantId = traffkTenantFinder.GetTenantIdAsync().ExecuteSynchronously();
                var tenantIdClaim = bearerTokenIdentity.Claims.First(x => x.Properties.Values.Contains(JwtRegisteredClaimNames.Sub));
                var tenantIdInToken = int.Parse(tenantIdClaim.Value);

                if (tenantIdInToken == currentTenantId)
                {
                    await Next(context);
                    return;
                }

                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                return;
            }

            //No bearer token in use, move on
            await Next(context);
        }

to .NET Core 2.0

        public async Task Invoke(HttpContext context, ITraffkTenantFinder traffkTenantFinder)
        {
            var authenticateInfo = await context.AuthenticateAsync("Bearer");
            var bearerTokenIdentity = authenticateInfo?.Principal;

            if (bearerTokenIdentity != null)
            {
                var currentTenantId = traffkTenantFinder.GetTenantIdAsync().ExecuteSynchronously();
                var tenantIdClaim = bearerTokenIdentity.Claims.First(x => x.Properties.Values.Contains(JwtRegisteredClaimNames.Sub));
                var tenantIdInToken = int.Parse(tenantIdClaim.Value);

                if (tenantIdInToken == currentTenantId)
                {
                    context.User = bearerTokenIdentity;
                    await Next(context);
                    return;
                }

                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                return;
            }

            //No bearer token in use, move on
            await Next(context);
        }

nearly identical except for

context.User = bearerTokenIdentity;

Without this change, we couldn't use context.User which was needed in ApiAuthorizationAttribute. Code for ApiAuthorizeAttribute HandleRequirementAsync below (no changes made during upgrade from 1.X to 2.0):

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ApiRequirement requirement)
        {
            var claims = context.User.FindAll(c => c.Type == requirement.ClaimType).ToList();
            if (claims.Count == 0) return Task.FromResult(0);
            foreach (var c in claims)
            {
                var pcv = PermissionClaimValue.CreateFromJson(c.Value);
                if (pcv.Granted)
                {
                    context.Succeed(requirement);
                    break;
                }
            }
            return Task.FromResult(0);
        }

And an example of usage of the attribute:

    [Authorize]
    [ApiAuthorize(ApiNames.Base)]
    [ApiAuthorize(ApiNames.Rx)]
    [Produces("application/json")]
    [Route("api/v1")]
    public class ExampleApiController : BaseApiController

Hope this helps someone out there.

@heku
Copy link

heku commented Sep 18, 2017

@HaoK I think the Microsoft.AspNet.Authentication.Core should be Microsoft.AspNetCore.Authentication

@alexvy86
Copy link

Anyone has any ideas on the following? I'm using Cookies and Jwt for different parts of my app, with Cookies being the DefaultAuthenticationScheme. I also have a custom middleware that checks if HttpContext.User.Identity.IsAuthenticated is true, to grab information from the claims and bulid an app-specific Context object (like an HttpContext) to be injected into my Controllers, Services, etc. The claims could be in either the cookie or the token.

When authenticating with Cookies everything works. I tried @davidfowl's suggestion of using the AuthenticationSchemes property of the Authorize attribute for my Jwt action methods, and while the request does get authorized and goes through, the middleware sees HttpContext.User.Identity.IsAuthenticated as false so the Context object isn't created... Is that expected? I understand that the DefaultAuthenticationScheme sets IsAuthenticated when it runs, but I thought if I forced another AuthenticationScheme in an Authorize attribute, then that one would get its chance to set the Principal and everything else in HttpContext... or does it run later in that case? I.e. right before the Controller action executes?

SvenEV added a commit to HiP-App/HiP-DataStore that referenced this issue Sep 23, 2017
* Removed some types (moved to lib)

* Work in progress

* Moved more functionality to ESLib

* Removed migration notice (moved to ESLib)

* Projects now reference NuGet package "HiP-EventStoreLib" from our MyGet-feed

* Now using HiP-EventStoreLib v0.3.0 (class InMemoryCache)

* Minor changes

* Upgraded projects to .NET Core 2.0, updated HiP-EventStoreLib to 0.6.0

* Update HiP-EventStoreLib to v0.7.0

* Updated dockerfile (now uses .NET Core 2.0 image)

* Updated NuGet.Config

* Updated docker file

* Moved NuGet.Config

* Updated dockerfile

* Moved NuGet.Config again

* Moved MongoDB primitives (DocRef<>) to HiP-EventStoreLib, updated HiP-EventStoreLib to v0.9.0

* Updated NuGet dependencies, migrated authentication configuration in Startup class to new 2.0 authentication stack (see aspnet/Security#1310)

* Updated HiP-EventStoreLib to v0.10.0, updated EventStore.ClientAPI.NetCore to v4.0.2-rc

* Bugfix for Swagger UI

* Added validation of page IDs when creating/updating exhibits

* Added validation of audio ID when creating/updating exhibit pages

* Bug fix: Timestamp assignment was missing when emitting certain events

* Fixed minor merging issues
SvenEV pushed a commit to HiP-App/HiP-DataStore that referenced this issue Sep 25, 2017
* Implemented page reordering via PUT /Exhibits/{id}, marked old event types as [Obsolete]

* ExhibitResult now makes use of the new Pages property

* Fixed a validation bug

* Removed unnecessary reference addition

* Pages no longer create a reference to the containing exhibit

* Simplified exhibit usage checks, reworked the areas that expected Exhibit.Referencees to contain page references (new functionality in ExhibitPageIndex is now used instead)

* Added stream metadata class (WIP)

* Implemented a new migration concept ("stream migration") that allows versioning of streams. Implemented such a "stream migration" that updates the event stream to support the new page ordering feature.

* Fixed: *Update-events were clearing the Referencees-list of the corresponding entity (thus e.g., only the pages created/updated after the latest ExhibitUpdate are shown for the exhibit)

* Bug fix in migration

* Migration fix

* Fixed ReSharper issues

* Minor documentation improvement

* Added Score Board class

* Cleanup of entity classes, more error handling

* Added missing [ProducesResponseType(400)]

* Hotfix

* Hotfix: DocRefList<T> was serialized as array (missing the fields 'Collection' and 'Database')

* Fixed a typo which caused the HTTP-route for tag deletion to be malformed (#36)

* Hotfix for updating exhibits

* Fixed: Whenever updating media, 'File' was set to null

* Changes to make pages "independent" from exhibits

* Implemented migration

* Added a warning when connecting to a production database during a debug session

* Added Score Record
Added API Calls
Fixed Sorting in Score Board

* Fixed errors

* Fixed R# error
Added validation for PUT API call

* Removed unused directive

* Removed unnesosry return types

* Minor clean-up

* Added Distinct()-calls to better handle duplicate IDs in JSON request arrays, added field ExhibitPageResult.Used

* Events are now emitted in batches by using transactions

* Bug fix

* Added migration code to make exhibits reference pages

* Removed usings

* Added removing file from storage (#40)

* TagsController.PostAsync now returns "201 Created" instead of "200 Ok", added some documentation

* Improved logging

* Bugfix in ReferencesIndex

* Reverted accidental change in *.DotSettings-file

* URL to "swagger.json" file is now configurable

* Renamed 'ContentBase.Referencees' to 'Referencers' to better reflect the meaning of that property

* HIPCMS-735: Remove status limitations when creating/updating entities (#44)

* Removed requirement for referenced entities to be published when creating or updating entities.

* Removed unused "using"

* HIPCMS-722: Implemented GET-methods for obtaining reference info (#45)

* Implemented "GET /api/<resource type>/<id>/Refs"-methods for obtaining reference info (or "usage info") for resources

* Added missing [ProducesResponseType(400)] attributes

* Removed empty lines

* Renamed ReferenceInfoResult properties for less confusion

* Added class OrderedSet

* DocRefList.Ids is now an ordered set, fixed DocRefExtensions.LoadAll(): now preserves order of IDs

* Hipcms 682 (#42)

* Added Rating System
--Rating system can be aplied to any ResourceType
--Added rating to Exhibits

* Added minor cleanup

* R# Error fixing

* R# ResourceType  redundance fixing

* Added changes:
--changed all url form 'rating' to 'Rating'
--public fields instead of properties.
--changed URL for exhibit/rating

* --Added savings of all user`s ratings in memory.
--Changed rating type to byte

* R# Error fixing

* No Storing Ratings in storage
Min-Max Rating now can be defined in one place

* Added stream migration, added cascading timestamps functionality to ReferencesIndex

* Modified controllers to return cascading timestamp instead of "flat" timestamp

* CRUD event types can now be excluded from the timestamp cascade mechanism

* Minor fix

* Fixed a bug

* Added Length Parameter to Call (#48)

API/ScoreBoard

* Completely removed ReferenceAdded/Removed events (necessary for correct timestamp propagation)

* Iss hipcms 670 testing (#50)

* HIPCMS-670

Project setup and first test.

* HIPCMS-670-Tests

Tests for tags controller.

* HIPCMS-670-Tests-For-Tags

* Implemented a very simple mechanism for accessing MVC services in tests (TestStartup makes services available in static MvcTestContext class)

* HIPCMS-670

Donw with tags tests

* Minor fixes

* Improved StreamMigrationArgs: When appending events they are now directly serialized to EventData (to prevent further modifications)

* Added a small bug-correcting migration

* fix in CorrectExhibitsMigration, reverted some changes of previous commit

* Fixed ReSharper warnings

* Renamed migrations so its easier to see their order

* Removed unnecessary file

* Updated some comments

* Added "Rank" field in response to API call api/ScoreBoard/{id} (#51)

* Added Rank field in response to API call api/ScoreBoard/{id}

* Fix R# errors

* Iss hipcms 729 (#52)

* Updated the webservice lib dependency to 2.0

* Setup auth0 usage

* Ignored .vscode folder

* Removed the old WebserviceLib dependency

* Set up all the controllers to use authorization & fixed a typo in the settings

* Implement authorization policies via scopes

* add missing library file

* renamed to lowercase

* Update HiP-DataStore.csproj

Test: Set webservice lib version to 1.0.0

* Revert "Update HiP-DataStore.csproj"

This reverts commit 72f8f17.

* used the public hip feed on myget.org

* Removed the webservice lib pkg - will be added via nuget feed on myget.org

* Copied nuget config to project root folder

* Removed obsolete nuget config and fixed a R# issue

* Fixed the docker build by referencing the dockerfile in the root folder

* Revert "Iss hipcms 729 (#52)" (#53)

This reverts commit c8deab6.

* Removed some types (moved to lib)

* Clean-up

* Clean-up

* Clean-up

* Bug fix

* Bug fixes

* Fixed ReSharper warnings

* Work in progress

* Add long domain to the CORS settings

* Moved more functionality to ESLib

* Removed migration notice (moved to ESLib)

* Projects now reference NuGet package "HiP-EventStoreLib" from our MyGet-feed

* Now using HiP-EventStoreLib v0.3.0 (class InMemoryCache)

* Iss hipcms 729 (#54)

* Updated the webservice lib dependency to 2.0

* Setup auth0 usage

* Ignored .vscode folder

* Removed the old WebserviceLib dependency

* Set up all the controllers to use authorization & fixed a typo in the settings

* Implement authorization policies via scopes

* add missing library file

* renamed to lowercase

* Update HiP-DataStore.csproj

Test: Set webservice lib version to 1.0.0

* Revert "Update HiP-DataStore.csproj"

This reverts commit 72f8f17.

* used the public hip feed on myget.org

* Removed the webservice lib pkg - will be added via nuget feed on myget.org

* Copied nuget config to project root folder

* Removed obsolete nuget config and fixed a R# issue

* Fixed the docker build by referencing the dockerfile in the root folder

* Changed API audience to WebAPI

(temp. fix for multiple-audience-problem)

* Changed audience and scopes definitions to use the new generic API def.

* Minor changes

* Upgraded projects to .NET Core 2.0, updated HiP-EventStoreLib to 0.6.0

* Update HiP-EventStoreLib to v0.7.0

* Updated dockerfile (now uses .NET Core 2.0 image)

* Updated NuGet.Config

* Updated docker file

* Moved NuGet.Config

* Updated dockerfile

* Moved NuGet.Config again

* Iss hipcms 667 (#56)

* Updated the webservice lib dependency to 2.0

* Setup auth0 usage

* Ignored .vscode folder

* Removed the old WebserviceLib dependency

* Set up all the controllers to use authorization & fixed a typo in the settings

* Implement authorization policies via scopes

* add missing library file

* renamed to lowercase

* Update HiP-DataStore.csproj

Test: Set webservice lib version to 1.0.0

* Revert "Update HiP-DataStore.csproj"

This reverts commit 72f8f17.

* used the public hip feed on myget.org

* Removed the webservice lib pkg - will be added via nuget feed on myget.org

* Copied nuget config to project root folder

* Removed obsolete nuget config and fixed a R# issue

* Fixed the docker build by referencing the dockerfile in the root folder

* Changed API audience to WebAPI

(temp. fix for multiple-audience-problem)

* Changed audience and scopes definitions to use the new generic API def.

* Added Roles and Id Substraction

* Added UserPermission Class

* Added Permissions to Media

* Deleting all permissions from get queries becouse there is no user content yet.

* Added restriction to all Types (which CMS using)

* Fixed version conflict of package "System.ValueTuple"

* Solved ReSharper errors

* Fixed a ReSharper warning

* Fixed a ReSharper warning, replaced "TO DO" with "TODO" (this way comments appear in Visual Studio's Task List)

* Moved MongoDB primitives (DocRef<>) to HiP-EventStoreLib, updated HiP-EventStoreLib to v0.9.0

* Updated NuGet dependencies, migrated authentication configuration in Startup class to new 2.0 authentication stack (see aspnet/Security#1310)

* Updated HiP-EventStoreLib to v0.10.0, updated EventStore.ClientAPI.NetCore to v4.0.2-rc

* Bugfix for Swagger UI

* Added validation of page IDs when creating/updating exhibits

* Added validation of audio ID when creating/updating exhibit pages

* Bug fix: Timestamp assignment was missing when emitting certain events

* Added to User Score System and Rating System (Exhibit Ratings) User authefication using access token

* Added User ID (owner) to all type of content

* Added Migration. Where we set admin as owner of old content

* Applied all auth rules to API call : POST/PUT/DELETE
Store in memory UserId for every content

* Changed Implementation of all API Call: GET

* Implemented filtering by page type

* Fixed auth-related exceptions (GetUserIdentity() now returns a UserIdentity-struct, thus avoiding exceptions and null handling)

* Removed debugging code

* Syntax fix

* Reverted: GetUserIdentity() now returns string again (returning a struct and overriding the ==-operator apparently breaks translation from LINQ to Mongo queries)

* Fixed minor merging issues

* Update master (#37) (#65)

* Implemented page reordering via PUT /Exhibits/{id}, marked old event types as [Obsolete]

* ExhibitResult now makes use of the new Pages property

* Fixed a validation bug

* Removed unnecessary reference addition

* Pages no longer create a reference to the containing exhibit

* Simplified exhibit usage checks, reworked the areas that expected Exhibit.Referencees to contain page references (new functionality in ExhibitPageIndex is now used instead)

* Added stream metadata class (WIP)

* Implemented a new migration concept ("stream migration") that allows versioning of streams. Implemented such a "stream migration" that updates the event stream to support the new page ordering feature.

* Fixed: *Update-events were clearing the Referencees-list of the corresponding entity (thus e.g., only the pages created/updated after the latest ExhibitUpdate are shown for the exhibit)

* Bug fix in migration

* Migration fix

* Fixed ReSharper issues

* Minor documentation improvement

* Cleanup of entity classes, more error handling

* Added missing [ProducesResponseType(400)]

* Hotfix

* Hotfix: DocRefList<T> was serialized as array (missing the fields 'Collection' and 'Database')

* Fixed a typo which caused the HTTP-route for tag deletion to be malformed (#36)

* HIPCMS-761: Moved event sourcing fundamentals to HiP-EventStoreLib (#57)

* Removed some types (moved to lib)

* Work in progress

* Moved more functionality to ESLib

* Removed migration notice (moved to ESLib)

* Projects now reference NuGet package "HiP-EventStoreLib" from our MyGet-feed

* Now using HiP-EventStoreLib v0.3.0 (class InMemoryCache)

* Minor changes

* Upgraded projects to .NET Core 2.0, updated HiP-EventStoreLib to 0.6.0

* Update HiP-EventStoreLib to v0.7.0

* Updated dockerfile (now uses .NET Core 2.0 image)

* Updated NuGet.Config

* Updated docker file

* Moved NuGet.Config

* Updated dockerfile

* Moved NuGet.Config again

* Moved MongoDB primitives (DocRef<>) to HiP-EventStoreLib, updated HiP-EventStoreLib to v0.9.0

* Updated NuGet dependencies, migrated authentication configuration in Startup class to new 2.0 authentication stack (see aspnet/Security#1310)

* Updated HiP-EventStoreLib to v0.10.0, updated EventStore.ClientAPI.NetCore to v4.0.2-rc

* Bugfix for Swagger UI

* Added validation of page IDs when creating/updating exhibits

* Added validation of audio ID when creating/updating exhibit pages

* Bug fix: Timestamp assignment was missing when emitting certain events

* Fixed minor merging issues

* Revert "HIPCMS-761: Moved event sourcing fundamentals to HiP-EventStoreLib (#57)" (#66)

This reverts commit 57e8ae7.

* Modified project file and Dockerfile (background: I was wondering why commands like "dotnet build" would not automatically pickup the "NuGet.Config" from our repo root folder. Turns out such commands were only looking for a "NuGet.Config" in the project folder because that was explicitly specified in the project file)

* Dockerfile changes (this should fix the libssl error; apparently, the Docker image "dotnet:2.0.0-sdk" is based on Debian Stretch instead of Debian Jessie like older versions, so we now use "dotnet:2.0.0-sdk-jessie")

* Update master (#37) (#68)

* Implemented page reordering via PUT /Exhibits/{id}, marked old event types as [Obsolete]

* ExhibitResult now makes use of the new Pages property

* Fixed a validation bug

* Removed unnecessary reference addition

* Pages no longer create a reference to the containing exhibit

* Simplified exhibit usage checks, reworked the areas that expected Exhibit.Referencees to contain page references (new functionality in ExhibitPageIndex is now used instead)

* Added stream metadata class (WIP)

* Implemented a new migration concept ("stream migration") that allows versioning of streams. Implemented such a "stream migration" that updates the event stream to support the new page ordering feature.

* Fixed: *Update-events were clearing the Referencees-list of the corresponding entity (thus e.g., only the pages created/updated after the latest ExhibitUpdate are shown for the exhibit)

* Bug fix in migration

* Migration fix

* Fixed ReSharper issues

* Minor documentation improvement

* Cleanup of entity classes, more error handling

* Added missing [ProducesResponseType(400)]

* Hotfix

* Hotfix: DocRefList<T> was serialized as array (missing the fields 'Collection' and 'Database')

* Fixed a typo which caused the HTTP-route for tag deletion to be malformed (#36)

* Hotfix (update HiP-EventStoreLib to v0.13.0) (#69)

* Updated HiP-EventStoreLib to v0.11.0

* Updated HiP-EventStoreLib to v0.13.0
@sschleicher208
Copy link

sschleicher208 commented Oct 7, 2017

I have Jwt and Facebook authentication enabled on a Core 2.0 Web API (Jwt is default scheme). When I pass Jwt token in the authorization header, works perfectly. When I pass the access token in the authorization header that I received from facebook, it fails. I've seen several articles from people about the same issue and I've banged my head against this for days on end. I must be missing something fundamental. Any ideas or direction?
services.AddAuthentication(o => { o.DefaultAuthenticateScheme = "Jwt"; o.DefaultChallengeScheme = "Jwt"; }) .AddJwtBearer("Jwt", o => { o.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = false, ValidAudience = AppSettings.TokenAudience, ValidateIssuer = false, ValidIssuer = AppSettings.TokenIssurer, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppSettings.TokenPrivateKey)), ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5) }; }) .AddFacebook(o => { o.AppId = AppSettings.FacebookAppID; o.AppSecret = AppSettings.FacebookAppSecret; o.SaveTokens = true; });

@irowbin
Copy link

irowbin commented Oct 7, 2017

@sschleicher208 I think you have to remove the default and add like this

services.AddAuthentication()
    .AddJwtBearer("jwt", o =>   {...})
    .AddFacebook("facebook", o =>   {...});

let me know if it works.

@sschleicher208
Copy link

sschleicher208 commented Oct 7, 2017

Thanks for the quick response. This didn't work, but maybe it's my approach. My web api is configured with the appID and secret from facebook. I have a test client that uses the HttpClient to issue rest commands against the API to call the Values controller which has an authorize attribute on it. When testing the Jwt, I generate a token from my API, attach it to the authorization header (bearer), and then call the values action which works just fine.

Next I used facebook tools to generate an access token for my app. I then fired up the same console app that I used for the Jwt test but populate the authentication header with the access token but this comes back with a 404 not found.

Maybe I cannot just put the access token generated from facebook in the authorization header and expect the API to know that this token is from facebook so use the facebook handler instead of the JWT. Is this even a valid test case? I guess my thought was that if anyone made a request that had this access token in the authentication header, the API would allow it to access actions protected by the authorize attribute. Am I mistaken about it being able to support this?

@ThatDeveloper
Copy link

ThatDeveloper commented Oct 9, 2017

var info = await context.Authentication.GetAuthenticateInfoAsync(CookieAuthenticationDefaults.AuthenticationScheme);
info.Properties.StoreTokens(tokens);
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, info.Principal, info.Properties);

How can I do this in .Net Core 2?

@HaoK
Copy link
Member Author

HaoK commented Oct 9, 2017

@ThatDeveloper context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme) is the replacement for GetAuthenticateInfoAsync

@Tratcher
Copy link
Member

Tratcher commented Oct 9, 2017

@sschleicher208 That's not what the Facebook handler is for. See #1439.

@sschleicher208
Copy link

@Tratcher Yea I eventually figured that out. What I do now is just issue a JWT after a successful external login callback from any external provider. Does that sound right? Seems like that is what is happening behind the scenes when using the azure user store as well. Correct?

@Tratcher
Copy link
Member

Tratcher commented Oct 9, 2017

@sschleicher208 Yes, that's what you want to do.

@xandhen
Copy link

xandhen commented Oct 11, 2017

I'm having some serious issues with MVC AuthorizeFilter in 2.0, a setup I had working in 1.1.

I'm using cookie authentication, but without Identity. I've made changes to my Startup.cs file so
In my Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory). I do
app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
And in public void ConfigureServices(IServiceCollection services) I do
`{

services.AddAuthentication("se.xxx.authentication")
	.AddCookie(options =>
	{
		options.Cookie.Name = "se.xxx.authentication";
    	
	});  
                  
services
	.AddMvc(config =>
	{
		var policy = new AuthorizationPolicyBuilder()
			.RequireAuthenticatedUser()
			.Build();
		config.Filters.Add(new AuthorizeFilter(policy));
		
	});

}`
When running my application, I immediately get this exception throw.

InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

I then tried adding the scheme to the MVC policy
{

services.AddAuthentication("se.xxx.authentication")
	.AddCookie(options =>
	{
		options.Cookie.Name = "se.xxx.authentication";
    	
	});  
                  
services
	.AddMvc(config =>
	{
		var policy = new AuthorizationPolicyBuilder(new string[] {"se.xxx.authentication"})
			.RequireAuthenticatedUser()
			.Build();
		config.Filters.Add(new AuthorizeFilter(policy));
		
	});

}`
And the exception is immediately changed to this one which I can not seem to adress in any way

InvalidOperationException: No authentication handler is configured to authenticate for the scheme: se.xxx.authentication

@HaoK
Copy link
Member Author

HaoK commented Oct 11, 2017

The authentication scheme is actually CookieAuthenticationDefaults.AuthenticationScheme or "Cookies" in the code you are showing. Setting the cookie name doesn't change the scheme name. There's a different overload of AddCookie which takes the scheme name

@Tratcher
Copy link
Member

Locking this closed issue. Please direct questions to the discussion thread #1338 or open new issues.

@aspnet aspnet locked and limited conversation to collaborators Oct 12, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests