ASP.NET Core 3.1 Use both OpenIDConnect and Custom Cookie Authentication - asp.net-core

I have an existing application that makes use of Cookie Authentication, and would like to add the ability to authenticate users using Active Directory. The current application uses Cookie based authentication and custom authorisation - roles in a database.
I am adding bits from example located here:
Add sign-in with Microsoft to an ASP.NET Core web app
When I run the application I get an error:
System.InvalidOperationException: Scheme already exists: Cookies
What is the correct way to configure OpenIdConnect and Cookie Authentication.
// STEP 1 Basic Cookie Auth
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Auth";
options.AccessDeniedPath = "/Home/AccessDenied";
options.Cookie.IsEssential = true;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromSeconds(day/2.0);
options.Cookie.HttpOnly = true; // not accessible via JavaScript
options.Cookie.Name = "login_token";
options.TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters);
});
// STEP 2 OpenID Connect Auth
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "Cookies", true);
I am not able to find any examples using both Cookie Authentication and OpenID Connect. Is this possible? Allowing users to login selectively using Active Directory authentication, or local authentication (details stored in local database).
After changing the "Cookie" name, get's rid of the error message,
but breaks the local authorisation, e.g.
When a valid Username and Password is given, I typically
authorise the login.
HttpContext.Response.Cookies.Append("login_token", token, GetCookieOptions());
Currently with OpenIDConnect configured User.Identity.IsAuthenticated
remains false.

According to the error messages, it tell you that you have multiple Scheme which named cookies.
According to the AddMicrosoftIdentityWebApp Method document, you could find the third parameter name is the cookieScheme.
The cookie-based scheme name to be used. By default it uses "Cookies".
But you have already set this name at above, so you should use other one. For example: "ADCookies".
Like below:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "OpenIdConnect", "ADCookies", true);

The mixed approach is a minefield but the below is allowing use to Authenticate Users via IdentityServer4 using OIDC while authenticating the Application into AzureAD with Identity.Web to get tokens for Api calls.
services.AddAuthentication(options =>
{
options.DefaultScheme = "IS4Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("IS4Cookies")
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
options.SignInScheme = "IS4Cookies";
// Get IdentityServer configuration from appsettings.json.
var config = Configuration.GetSection("IdentityServerOptions").Get<IdentityServerOptions>();
options.Authority = config.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = config.ClientId;
options.ClientSecret = config.ClientSecret;
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapJsonKey("role", "role");
options.ClaimActions.MapJsonKey("role", System.Security.Claims.ClaimTypes.Role);
options.ClaimActions.MapJsonKey("email", "email");
options.ClaimActions.MapJsonKey("preferred_username", "preferred_username");
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.FromResult(0);
}
};
})
.AddMicrosoftIdentityWebApp(Configuration, "AzureOptions")
.EnableTokenAcquisitionToCallDownstreamApi(new string[]{"sms.all" })
.AddInMemoryTokenCaches();

This is what I use and it works, you just need to specify the configureCookieAuthenticationOptions and set the name inside there and you should be good to go, also I had to use lax for SameSite or it would not work for me.
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(identityOptions =>
/* {identityOptions.ClientId ="";}, // if you want to specify the options manually instead of Configuration.GetSection() call*/
Configuration.GetSection("AzureAd"),
configureCookieAuthenticationOptions: authCookie => { // Setup SSO cookie
authCookie.Cookie.Name ="Your.Cookie.Name.Here";// change name to hide .net identifiers in name
authCookie.Cookie.HttpOnly = true;// make so client cannot alter cookie
authCookie.Cookie.SecurePolicy = CookieSecurePolicy.Always;// require https
authCookie.Cookie.SameSite = SameSiteMode.Lax;// from external resource
// verify options are valid or throw exception
authCookie.Validate();
}
);
You may or may not need all of the authCookie values here, but it should get you started in the right direction!

It's possible to mix two mechanisms.
I use MicrosoftIdentity authentication for access to administration web pages and cookies authentication for my APIs and SignalR hubs.
I use this in startup ConfigureServices
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie("CookiesApiScheme", options =>
{
options.SlidingExpiration = true;
// There is no redirection to a login page for APIs and SignalR Hubs, I just made a call to /Api/Login/SignIn with credential
options.AccessDeniedPath = new PathString("/Api/Login/AccessDenied"); // Action who just returns an Unauthorized
})
.AddMicrosoftIdentityWebApp(Configuration); // By default scheme is "CookieAuthenticationDefaults.AuthenticationScheme"
And in API controller you can use something like this
[Route("api/[controller]")]
[ApiController]
[Authorize(Roles = Roles.ADMIN)]
[Authorize(AuthenticationSchemes = "CookiesApiScheme")]
public class DefaultController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Based on this post: ASP.NET Core 2.0 AzureAD Authentication not working

Related

The mandatory 'code_challenge' parameter is missing with OpenIddict

I have an identity server running asp.net core 5, with openiddict 3.1.1
I'm having trouble where I'm getting the error from openiddict:
error:invalid_request
error_description:The mandatory 'code_challenge' parameter is missing.
error_uri:https://documentation.openiddict.com/errors/ID2029
in some scenarios, but not all. My identity server has a startup.cs of:
services.AddDbContext<IdentityContext>(options =>
{
options.UseSqlServer(dbConnectionString, x => x.UseNetTopologySuite());
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict<Guid>();
});
services.AddTransient<IPasswordHasher<ApplicationUser>, CustomPasswordHasher>();
services.AddTransient<IOptions<IdentityOptions>, CustomOptions>();
services.AddScoped<SignInManager<ApplicationUser>, CustomSignInManager>();
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<IdentityContext>()
//.AddDefaultUI()
.AddDefaultTokenProviders();
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default entities.
options.UseEntityFrameworkCore()
.UseDbContext<IdentityContext>()
.ReplaceDefaultEntities<Guid>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the authorization, device, logout, token, userinfo and verification endpoints.
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetLogoutEndpointUris("/connect/signout")
.SetTokenEndpointUris("/connect/token");
// Enable the client credentials flow.
options
.AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange()
.AllowRefreshTokenFlow();
// Encryption and signing of tokens
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption(); //TODO: not a huge deal as long as we're not hiding anything bad here.
// Expose all the supported claims in the discovery document.
options.RegisterClaims(Configuration.GetSection("OpenIddict:Claims").Get<string[]>());
// Expose all the supported scopes in the discovery document.
options.RegisterScopes(Configuration.GetSection("OpenIddict:Scopes").Get<string[]>());
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
.EnableAuthorizationRequestCaching()
.EnableLogoutEndpointPassthrough()
.EnableVerificationEndpointPassthrough()
.EnableTokenEndpointPassthrough();
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the ASP.NET Core host.
options.UseAspNetCore();
// Enable authorization entry validation, which is required to be able
// to reject access tokens retrieved from a revoked authorization code.
options.EnableAuthorizationEntryValidation();
});
with an OpenIDWorker of:
public async Task StartAsync(CancellationToken cancellationToken)
{
using IServiceScope scope = _serviceProvider.CreateScope();
IdentityContext context = scope.ServiceProvider.GetRequiredService<IdentityContext>();
await RegisterApplicationsAsync(scope.ServiceProvider, _configuration);
static async Task RegisterApplicationsAsync(IServiceProvider provider, IConfiguration configuration)
{
IOpenIddictApplicationManager manager = provider.GetRequiredService<IOpenIddictApplicationManager>();
string clientID = configuration.GetSection("OpenIddict:ClientId").Get<string>();
string clientSecretString = "blahblahblah";
if (await manager.FindByClientIdAsync(clientID) is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = clientID,
ClientSecret = clientSecretString,
ConsentType = ConsentTypes.Explicit,
DisplayName = configuration.GetSection("OpenIddict:DisplayName").Get<string>(),
PostLogoutRedirectUris =
{
new Uri("https://localhost:44330/signout-callback-oidc")
},
RedirectUris =
{
new Uri("https://localhost:44330/signin-oidc")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.Logout,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles,
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange
}
});
}
}
}
when i try to connect to the server with a C# razor app with the following startup.cs, it works fine and w/out any issues:
string clientSecretString = "blahblahblah";
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromMinutes(50);
options.SlidingExpiration = false;
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
// Note: these settings must match the application details
// inserted in the database at the server level.
options.ClientId = Configuration.GetSection("ClientId").Get<string>();
options.ClientSecret = clientSecretString;
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
// Use the authorization code flow.
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
// Note: setting the Authority allows the OIDC client middleware to automatically
// retrieve the identity provider's configuration and spare you from setting
// the different endpoints URIs or the token validation parameters explicitly.
options.Authority = "https://localhost:44330/";
options.Scope.Add("email");
options.Scope.Add("roles");
options.Scope.Add("offline_access");
options.SecurityTokenValidator = new JwtSecurityTokenHandler
{
// Disable the built-in JWT claims mapping feature.
InboundClaimTypeMap = new Dictionary<string, string>()
};
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
options.AccessDeniedPath = "/";
});
But when i try to connect to it with https://oidcdebugger.com/, or if I try to connect to it using Azure B2C user flows, I get the error message above about missing a code_challenge (see image )
What am i missing here? My plan was to use B2C, but i'm not seeing what is blocking me.
See https://github.com/openiddict/openiddict-core/issues/1361 for reference. Issue ended up being a problem with B2C itself, and will require a fix for them

Can I use openid connect authentication and jwtbearer authentication scheme in one app?

I have an ASP.NET Core web app where the front-end is built in react and for the back-end, I have web APIs, both the things are placed in one web project.
for authentication, I used Azure AD open Id connect authentication scheme.
Now I have a requirement where I need to expose some APIs to an external system using client credentials flow. so I am not sure how to implement this in the current web app as it already has the open id connect authentication scheme.
Any idea?
Many thanks
You need to create two applications in Azure portal. At present, you already have an ASP.NET Core Web application, which represents the front-end application, so you need to create another application that represents the Web API, that is, the back-end application.
First, you need to expose the api of the back-end application protected by Azure, which can be configured according to the following process:
Azure portal>App registrations>Expose an API>Add a scope>Add a client application
Then you need to create the appRole of the back-end application, and then grant that role as an application permission to the client application.
Next, go to client application>API permissions>Add a permission>My APIs>your api application.
Finally, you need to obtain an access token using the client credential flow where no user is logged in:
Parse the token:
Finally, you can pass the token to the api application, and the api application will authenticate the client application by parsing the token.
Similar samples.
I had the same case and solved it this way:
Startup.cs
string[] scopes = new[]
{
"openid",
"profile",
"offline_access",
"email",
"scope_one",
"scope_two",
};
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://my-own-indentity.com";
options.Audience = "Any_Audience";
options.RequireHttpsMetadata = false;
options.SaveToken = true;
}) .AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://Authority.com";
options.ClientId = "*****";
options.ClientSecret = "******";
options.CallbackPath = "/callback";
options.ResponseType = "code";
options.ResponseType = OidcConstants.ResponseTypes.Code;
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.Scope.Clear();
options.GetClaimsFromUserInfoEndpoint = true;
foreach (string scope in scopes)
{
options.Scope.Add(scope);
}
});
To be authenticated by "AddJwtBearer" for controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("[controller]")]
[ApiController]
public class ProjectController : ControllerBase
{
//your code
}
To be authenticated by "AddOpenIdConnect" for controller::
[Authorize(AuthenticationSchemes = "oidc")]
[ApiController]
[Route("api/[controller]")]
public class SomethingWithOpenIdController : Controller
{
//your code
}
If you found another solution feel free to share, otherwise I hope this helps you.
In Startup.cs
// Add services to the container.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, (options) =>
{
options.TokenValidationParameters.ValidateIssuer = true;
options.MetadataAddress = "metadataaddress";
options.TokenValidationParameters.ValidAudience = "portal.com";
// or any specific option used for your application
});
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(
options =>
{
builder.Configuration.Bind("AzureAd", options);
// or any specific option used for your application
});
In Controllers:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme + "," + OpenIdConnectDefaults.AuthenticationScheme)]
[ApiController]
[Route("[controller]")]
public class SomeController : ControllerBase
This worked for me :)

Using OpenIdConnect and JwtBearer authentication together

My ASP.NET Core 2.2 web site uses OpenIdConnect to connect to an external OIDC provider for authentication. So we get a redirect to the provider, you log in, it returns back to the site. All of this is handled server side and works great.
Our web site uses javascript to communicate to the API backend, which is decorated with an [Authorize] attribute.
Now, we want to share our API with another application. I am having trouble understanding how to make it work with the existing OpenIdConnect provider. If I use Postman to make a call, I add a Bearer token but I am returned the log in screen for my OIDC provider. I understand this, it's set up for OIDC.
I have tried using AddJwtBearer with [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] set on my Controller and when I do that I can successfully pass in a Bearer token and call my API from Postman but I can't make a call from a javascript page on my site.
So the ultimate question is, how do I configure this site so that I can call an API from my site itself with OIDC and also call from an external app using a Bearer token?
The code looks like this:
public void ConfigureServices(IServiceCollection services)
{
var authbuilder = services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});
authbuilder.AddCookie(p =>
{
p.SlidingExpiration = true;
p.Events.OnSigningIn = (context) =>
{
context.CookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(14);
return Task.CompletedTask;
};
});
authbuilder.AddOpenIdConnect(options =>
{
options.Authority = Configuration["OpenIdConnectSettings:AuthorityUrl"];
options.ClientSecret = Configuration["OpenIdConnectSettings:ClientSecret"];
options.ClientId = Configuration["OpenIdConnectSettings:ClientId"];
}
options.SaveTokens = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.GetClaimsFromUserInfoEndpoint = true;
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
// validation logic omitted
}
EDIT: Any external apps will also use the same OIDC provider. It's just a matter of getting my API Controllers to work from the web site itself and being called from another app that uses the same OIDC provider.
EDIT: I think I may have gotten it to work by adding this code:
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
if the other application is a service/application (not a human facing client) then that application needs to register as a client in your OIDC provider and then preferably use the client credentials flow, to get a token from your OIDC provider and pass it to the API.
The API only trusts access tokens from your OIDC provider (the token issuer), so the application needs to authenticate against the app and to get a proper access token.

How to setup WASM IdentityServer with user/pass authentication AND device authentication with client/secret from a console application

I have a typical Blazor WASM project, Server, Client and Shared. Authentication with IdentityServer is setup and working correctly. I'm receiving the JWT when I login with a user and I can get the discovery document.
Aside from normal users, I want to connect devices. These devices are not users so it seems wrong to create a user for each device. If I create a user then I can login and get the token. But I added InMemoryClients, so I was under the assumption that I could login as a client with id/secret using the device authentication endpoint of the discovery document.
I added a console application that is running on the device. But authentication fails returning "invalid_client".
In the server app I defined a Client and ApiScope:
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api", "My API")
};
public static IEnumerable<IdentityServer4.Models.Client> Clients =>
new List<IdentityServer4.Models.Client>
{
new IdentityServer4.Models.Client
{
ClientId = "device",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret")
},
// scopes that client has access to
AllowedScopes = { "api" }
}
};
They are added with the AddInMemory functions in the Startup.Configure method:
services.AddIdentityServer()
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:44316";
options.Audience = "api";
})
.AddIdentityServerJwt();
In the console app I send a DeviceAuthenticationRequest:
static IDiscoveryCache _cache = new DiscoveryCache("https://localhost:44316");
var disco = await _cache.GetAsync();
if (disco.IsError) throw new Exception(disco.Error);
var client = new HttpClient();
response = await client.RequestDeviceAuthorizationAsync(new DeviceAuthorizationRequest
{
//Address = disco.TokenEndPoint, // same result
Address = disco.DeviceAuthorizationEndpoint,
ClientId = "device",
ClientSecret = "secret",
Scope = "api"
});
The response in the client contains an error, "invalid_client". And the server complains also:
IdentityServer4.Validation.ClientSecretValidator: Error: No client with id 'client' found. aborting
I hope my question is clear, thanks in advance.
EDIT
Obviously the typo was a problem. But I managed to fix it by changing the AllowedGrantTypes to:
AllowedGrantTypes = { GrantTypes.ClientCredentials, GrantTypes.DeviceFlow };
However! I had to removed api authorization, need to figure out how to add it back:
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
And instead of specifying options for and adding JwtBearer:
//services.AddAuthentication("Bearer")
// .AddJwtBearer("Bearer", options =>
// {
// options.Authority = "https://localhost:44316";
// options.Audience = "api";
// })
// .AddIdentityServerJwt();
I just have this:
services.AddAuthentication()
.AddIdentityServerJwt();
I think you have just a small typo. In the Startup.cs the ClientId = "device". In your client, you set ClientId = "client".
In general, you are right. You don't need to create a client for each user and their potential devices. The definition of one client for all possible users and devices is enough. You could even debate if a client's password is necessary and increase security. In the official docs, the secret is optional.
In a simplified view: the "client part" of this authentication flow is mainly used to generate the user code. In a second (asynchronous) step, a user with different device logins to authenticate the device by entering the previously generated code.
For more details of a look at this post.

Multiple login pages for different claims

Here is my situation.
To do some actions on the web site user should be authenticated. Different actions require different claims. For example, to make an order user is authenticated by phone number only, to view the purchase history user should be authenticated by phone number and password, and to change the phone number user should be authenticated using two-factor authentication.
I create a login page for each of the authentication methods and when user is authenticated I give her a set of claims depending on the authentication method.
I add [Authorize(Policy="CanCreateOrder")] to the CreateOrder action method. The policy has the logic what claims required to authorize user. In case user is not authorized I want to redirect the user to the appropriate login page.
The question is how I could specify the url where user should be redirected for authentication?
Looking at CookieAuthenticationMiddleware I could not see how to to specify login page depending on what claims required. Documentation suggests to set LoginPath property at the configuration time, but in my case login url depends on what claims I need to authorize the user.
You can use different authentication scheme for each different claim:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "Phone",
LoginPath = "<phone - path>"
....
}
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "Password",
LoginPath = "<password - path>"
....
}
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "TwoFactor",
LoginPath = "<twofactor - path>",
....
}
And then usage:
[Authorize(Policy="CanCreateOrder", ActiveAuthenticationSchemes = "Phone")]
Also you can use multiple scheme:
[Authorize(Policy="CanCreateOrder", ActiveAuthenticationSchemes = "Phone,TwoFactor")]
See https://docs.asp.net/en/latest/security/authorization/limitingidentitybyscheme.html
From aspnet core 2, use can use the attribute for the controller, action o razor page you need to configure:
[Authorize(Policy="CanCreateOrder")]
Configure your policy with the authenticationscheme
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy("CanCreateOrder",
authBuilder =>
{
authBuilder.AddAuthenticationSchemes("Management_Scheme");
authBuilder.RequireClaim("Manager");
});
});
...
}
and multiple cookie authentication configuration depending on the authentication scheme:
public void ConfigureServices(IServiceCollection services)
{
...
services
.AddAuthentication()
.AddCookie("Public_Scheme" , options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/logout";
})
.AddCookie("Management_Scheme",options =>
{
options.LoginPath = "/management/login";
options.LogoutPath = "/management/logout";
});
...
}
This extensions must be used inside ConfigureServices method and not Configure.
Take into account that configuring the authentiucation scheme inside the policy is not mandatory, it can be can addes to the authorization attribute wherever needed:
[Authorize("CanCreateOrders",AuthenticationSchemes = "ManagementScheme")]
You can see more about migrating in to 2.0 in this article.
This Is Perfect, The method I use for two different login authorizations
services.AddAuthentication(options =>
{
options.RequireAuthenticatedSignIn = false;
}).
AddCookie("CustomSchema1",
options =>
{
options.Cookie.Name = "CustomCookieName1";
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(10);
options.SlidingExpiration = true;
}).AddCookie("CustomSchema2", options =>
{
options.Cookie.Name = "CustomCookieName2";
options.LoginPath = "/writer/login";
options.ExpireTimeSpan = TimeSpan.FromDays(10);
options.SlidingExpiration = true;
});