Custom post Sign-in page and Sign-out page for Microsoft Identity platform - asp.net-core

I would like to use Microsoft Identity platform in my ASP.NET Core.NET 6 application with a custom sign in and sign out page.
In my case, i don't want to redirect automatically user to Microsoft sign in page but to my custom sign in page (that contain login button).Same for signout page.
I am using Microsoft.Identity.Web and Microsoft.Identity.Web.UI
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
var app = builder.Build();
how can I change the default behaviour ?

For example, I integrate azure ad in my asp.net core MVC project:
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
services.AddControllersWithViews()
// Add the Microsoft Identity UI pages for signin/out
.AddMicrosoftIdentityUI();
}
And I set the default home page to my HelloController, it will reder hello/index.cshtml to the page, with a button <a asp-action="index" asp-controller="home">Home</a>
And in my HomeController.cs, it has [Authorize] annotation on the controller, so when I didn't sign in with azure ad, it will redirect to microsoft sign in page. Then since I set the callback path as the home/index.cshtml, after sign in, I will go to home/index.cshtml.
My home controller:
using Microsoft.AspNetCore.Authorization;
[Authorize]
public class HomeController : Controller
{

Related

Updating session cookie on first request to a site and add to custom header on additional requests

I'm using ASP.NET Identity and OpenIddict for a custom authorization server in MVC / Razor Pages. I'm supporting most OpenID flows with OpenIddict and user and admin areas with ASP.NET Identity.
The site is accessed directly from desktop browsers, mobile apps and other projects, especially the endpoints for starting a login and a authroization flow.
Everything is working as expected.
Now I would like to add partial multi-tenancy by only switching CSS based on the tenant. The rest of the app will remain untouched. I was going to start by modifying the _Styles.cshtml file by adding the following:
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor accessor
#{
var ok = accessor.HttpContext.Request.Headers.TryGetValue("X-Tenant-Id", out var values);
var tenantId = values.FirstOrDefault();
}
Then depending on the tenant I will add the CSS file.
How, through the whole app, in views and in controller actions there redirects and links to other pages (eg. from login page to register page or forgot password). This is a problem because the custom header above is not persisted in further requests.
So I was thinking of added the header to the session state. Currently I'm not using sessions, but I would be doing something similar to what is described in this MS Article.
Any request that has the above header will update the session state. As this is an essential cookie but I still have to use cookie consent policy and data protection, is it possible to flag it as essential to avoid cookie consent?
Will the work or is there a better solution?
Is it possible to flag it as essential to avoid cookie consent
If you marked the session cookie as essential ,you would reveive a cookie name of .AspNetCore.Session
builder.Services.AddSession(options =>
{
options.Cookie.IsEssential = true;
})
and if you configured cookieConsent as below
builder.Services.Configure<CookiePolicyOptions>(op =>
{         op.CheckConsentNeeded = _ => true;     
});
you won't receive the cookies which are not marked as essential
I tried as below:
public IActionResult Index()
{
HttpContext.Response.Cookies.Append("SomeKey", "SomeValue" ,new CookieOptions() { IsEssential=false});
HttpContext.Session.SetString("SomeKey", "SomeVal");
return View();
}
public IActionResult Privacy()
{
string? cookieval;
HttpContext.Request.Cookies.TryGetValue("SomeKey", out cookieval);
var sessionval=HttpContext.Session.GetString("SomeKey");
return View();
The Result:

OIDC authentication in server-side Blazor

I used this method but somehow it's not right because #attribute [AllowAnonymous] doesn't really worked so I use [Authorized] attribute instead of [AllowAnonymous] and then remove RequireAuthenticatedUser but OIDC does not redirect client to server login page.
I checked SteveSanderson github article about authentication and authorization in blazor but he didn't talk about OIDC.
So how can I handle this?
Startup class:
services.AddAuthentication(config =>
{
config.DefaultScheme = "Cookie";
config.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookie")
.AddOpenIdConnect("oidc", config =>
{
config.Authority = "https://localhost:44313/";
config.ClientId = "client";
config.ClientSecret = "secret";
config.SaveTokens = true;
config.ResponseType = "code";
config.SignedOutCallbackPath = "/";
config.Scope.Add("openid");
config.Scope.Add("api1");
config.Scope.Add("offline_access");
});
services.AddMvcCore(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser() // site-wide auth
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
The following is a complete and working solution to the question:
First off, you'll need to provide an authentication challenge request mechanism that enables redirection to an authenticating agent such as IdentityServer. This is only possible with HttpContext, which is not available in SignalR (Blazor Server App). To solve this issue we'll add a couple of Razor pages where the HttpContext is available. More in the answer...
Create a Blazor Server App.
Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect -Version 3.1.0 or later.
Create a component named LoginDisplay (LoginDisplay.razor), and place it in the
Shared folder. This component is used in the MainLayout component:
<AuthorizeView>
<Authorized>
Hello, #context.User.Identity.Name !
<form method="get" action="logout">
<button type="submit" class="nav-link btn btn-link">Log
out</button>
</form>
</Authorized>
<NotAuthorized>
Log in
</NotAuthorized>
</AuthorizeView>
Add the LoginDisplay component to the MainLayout component, just above the About
anchor element, like this
<div class="top-row px-4">
<LoginDisplay />
About
</div>
Note: In order to redirect requests for login and for logout to IdentityServer, we have to create two Razor pages as follows:
Create a Login Razor page Login.cshtml (Login.cshtml.cs) and place them in the Pages folder as follow:
Login.cshtml.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
public class LoginModel : PageModel
{
public async Task OnGet(string redirectUri)
{
await HttpContext.ChallengeAsync("oidc", new
AuthenticationProperties { RedirectUri = redirectUri } );
}
}
This code starts the challenge for the Open Id Connect authentication scheme you defined in the Startup class.
Create a Logout Razor page Logout.cshtml (Logout.cshtml.cs) and place them in the Pages folder as well:
Logout.cshtml.cs
using Microsoft.AspNetCore.Authentication;
public class LogoutModel : PageModel
{
public async Task<IActionResult> OnGetAsync()
{
await HttpContext.SignOutAsync();
return Redirect("/");
}
}
This code signs you out, redirecting you to the Home page of your Blazor app.
Replace the code in App.razor with the following code:
#inject NavigationManager NavigationManager
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
#{
var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
NavigationManager.NavigateTo($"login?redirectUri={returnUrl}", forceLoad: true);
}
</NotAuthorized>
<Authorizing>
Wait...
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
Replace the code in the Startup class with the following:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddAuthorizationCore();
services.AddSingleton<WeatherForecastService>();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme =
OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://demo.identityserver.io/";
options.ClientId = "interactive.confidential.short";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.UseTokenLifetime = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.TokenValidationParameters = new
TokenValidationParameters
{
NameClaimType = "name"
};
options.Events = new OpenIdConnectEvents
{
OnAccessDenied = context =>
{
context.HandleResponse();
context.Response.Redirect("/");
return Task.CompletedTask;
}
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
IMPORTANT: in all the code sample above you'll have to add using statements as necessary. Most of them are provided by default. The using provided here are those necessary to enable the authentication and authorization flow.
Run your app, click on the log in button to authenticate. You are being redirected to IdentityServer test server which allows you to perform an OIDC login. You may enter a user name: bob and password bob, and after click the OK button, you'll be redirected to your home page. Note also that you can use the external login provider Google (try it). Note that after you've logged in with identity server, the LoginDisplay component displays the string "Hello, <your user name>".
Note: While you're experimenting with your app, you should clear the browsing data, if you want to be redirected to the identity server's login page, otherwise, your browser may use the cached data. Remember, this is a cookie-based authorization mechanism...
Note that creating a login mechanism as is done here does not make your app more secured than before. Any user can access your web resources without needing to log in at all. In order to secure parts of your web site, you have to implement authorization as well, conventionally, an authenticated user is authorized to access secured resource, unless other measures are implemented, such as roles, policies, etc. The following is a demonstration how you can secure your Fetchdata page from unauthorized users (again, authenticated user is considered authorized to access the Fetchdata page).
At the top of the Fetchdata component page add the #attribute directive for the Authorize attribute, like this: #attribute [Authorize]
When an unauthenticated user tries to access the Fetchdata page, the AuthorizeRouteView.NotAuthorized delegate property is executed, so we can add some code to redirect the user to the same identity server's login page to authenticate.
The code within the NotAuthorized element looks like this:
<NotAuthorized>
#{
var returnUrl =
NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
NavigationManager.NavigateTo($"login?redirectUri=
{returnUrl}", forceLoad: true);
}
</NotAuthorized>
This retrieves the url of the last page you were trying to access, the FetchData page, and then navigates to the Login Razor page from which a password challenge is performed, that is, the user is redirected to the identity server's login page to authenticate.
After the user has authenticated they are redirected to the FetchData page.
For server-side Blazor, authentication happens on the Razor page on which the Blazor application is hosted. For the default template, this is the _Host.cshtml Razor page which is configured to be the fallback page for server-side routing. Since the page is like a normal Razor page, you can use the [Authorize] or [AllowAnonymous] attributes there.
Any authorization you apply to the _Host.cshtml impacts how the general access to the Blazor app itself is authorized. If you want only authenticated users to access the app, you should require authorization; if you want any non-authenticated users to access the app, you cannot protect the app access itself.
The authorization of the page does not mean that you cannot have a more fine-grained authorization within your app. You can still use different rules and policies for particular components within your application. For that, you can use the <AuthorizeView> component.
There are two common scenarios that are likely for server-side Blazor:
Access to the whole Blazor application is limited to authenticated users. Users that are not authenticated should immediately authenticate (e.g. using OIDC) so that no anonymous user hits the app.
In that case, it should be enough to protect the _Host.cshtml by requiring authenticated users, either through the [Authorize] attribute, or using a convention in the AddRazorPages() call.
When accessing the Blazor application without being authenticated, the default authorization middleware will cause an authentication challenge and redirect to the OIDC sign-in.
Non-authenticated users should be able to access the Blazor application but the Blazor application will use a more detailed authorization using the <AuthorizeView> or IAuthorizationService.
In this situation, the _Host.cshtml must not be protected since anonymous users need to access it. This also means that the default authorization middleware, which runs as part of the Razor page, will not do anything. So you will have to handle the challenge yourself.
The “simple” way to do this would be to provide a login link to a different server-side route which will then trigger the authentication challenge and redirect to the OIDC sign-in. For example, you could have a MVC action like this:
[HttpGet("/login")]
public IActionResult Login()
=> Challenge();
Within your Blazor app, you could now add a link to this route and allow users to sign in that way:
<AuthorizeView>
<Authorized>
Signed in as #context.User.Identity.Name.
</Authorized>
<NotAuthorized>
Sign in here
</NotAuthorized>
</AuthorizeView>

Blazor Server / Asp.Net Core: Http Request doesn't pass user identity to MVC Controller when published on IIS

currently I'm having trouble getting identity information in my MVC controllers. It's no problem when debugging the blazor application locally but when I publish the application on IIS, I only get the identity of the executing service account, for instance "MachineName\ApplicationPoolIdentity" or "MachineName\LocalService" (depending on what was selected as identity in the application pool settings) in my MVC controllers. On the Blazor pages, however, authentication and authorization seems to work fine.
I got "Windows Authentication" enabled and "Anonymous Authentication" disabled in IIS site authentication settings.
I need the users identity for our audit trail implementation, which creates an entry for each crud operation.
In the MVC Controller I tried using "this.User.Identity" or "HttpContext.User.Identity", which is the same object. When debugging locally it shows the corect identity (of myself as caller). When deployed on IIS I get "MachineName\ApplicationPoolIdentity"
the MVC controller looks as such:
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class DatabaseSystemsController : CustomControllerBase
{
// GET: api/DatabaseSystems
[HttpGet("/api/AllDatabaseSystems")]
public async Task<ActionResult<IEnumerable<DatabaseSystem>>> GetAllDatabaseSystems()
{
try
{
var identity = HttpContext.User.Identity
...
return await _context.DatabaseSystems.ToListAsync();
}
catch (Exception)
{
...
}
}
}
I hope someone can help.
Thanks in advance
L.
To get the user to impersonate use the AuthenticationStateProvider and get the user from this and cast to a WindowsIdentity to retrieve the AccessToken.
This works in both a controller and a razor component.
Inject the AuthenticationStateProvider and then in your method use the following code:
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
var userToImpersonate = (WindowsIdentity)user.Identity;
await WindowsIdentity.RunImpersonatedAsync(userToImpersonate.AccessToken, async () =>
{
// Your Code in here to call your api
}

Restrict account registration to only Admin users with asp.net identity authentication

I am creating a Blazor server app that requires authenticated users in order to prevent external access, and I would like to limit the ability to register new accounts to be only available to Administrator users to prevent unwanted accounts from being created.
I'm using Identity user accounts, scaffolded out for Blazor. Solutions like this at least disable the registration, but from there I need to be able to enable it again for administrative users. I attempted to recreate the register page as a Blazor component, however, using the generated RegisterModel did not seem to work for me.
Upon a large amount of searching - the answer ended up being relatively simple. Muhammad Hammad Maroof's solution although technically correct, confused me and was mostly unhelpful for working with the register page specifically.
As I am using Role-Based Authentication scaffolded out from Blazor - in a seperate razor page I use this code to set up roles:
#code {
protected override async Task OnParametersSetAsync()
{
await SetUpAuth();
}
private async Task SetUpAuth()
{
const string Manager = "Manager";
string[] roles = { Manager };
foreach (var role in roles)
{
var roleExist = await roleManager.RoleExistsAsync(role);
if (!roleExist)
{
await roleManager.CreateAsync(new IdentityRole(role));
}
}
var user = await userManager.FindByEmailAsync(config.GetValue<string>("AdminUser"));
if (user != null)
{
await userManager.AddToRoleAsync(user, Manager);
}
}
}
Allowing the appropriate user to be marked as an administrator. This page has the [AllowAnonymous] tag on it in order to allow the administrative user as dictated by "AdminUser": "SomeEmail#test.com", in the appsettings.json page to be able to access the site on initial setup.
Preventing access to the Blazor site itself from anonymous users was as simple as adding this line to ConfigureServices in the startup class (Code taken from Microsoft Docs)
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
From this, allowing access to the register page was significantly easier than I had initially thought (likely due to my lack of .net experience). To do so, all you have to do is locate the Register.cshtml.cs page (I couldn't initially find the controller method Muhammad had mentioned) which I did by using visual studio to right click on the Register Model and then go to definition. This should take you to the Register.cshtml.cs page with the RegisterModel class. In order to restrict access to this page for only a specific role of users, all you have to do is change the [AllowAnonymous] tag above the class to look similar to this:
[Authorize(Roles ="Manager")]
public class RegisterModel : PageModel
It's important to note that the same technique used to secure the register page could be used to secure any of the of the other scaffolded Identity pages. For applications where you may have more than a few roles, the method provided by Muhammad of using policy based authorization may be the way to go, and this link he provided is a great tutorial for setting up and using that form of authentication.
//FORCE autentication for all RAZOR PAGES except [AllowAnonymous]
services.AddControllers(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Only adding this code to my startup.cs solved my problem.
Here's how I am doing it in asp.net core mvc app
C# Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(ADMIN_ACCESS, policy => policy.RequireRole($"{UserType.Admin}"));
});
}
[Authorize("AdminAccess")]
public class AdminController : Controller
{
//Some action methods here
}

Authentication for .NET Core Razor Pages application doesn't work for views without an "/Identity" route while using .AddIdentityServerJwt()

Using the .NET Core 3.1 framework, I'm trying to configure a web platform with the following setup:
A Razor Pages application, that acts as the landing page for the platform with features/pages such as advertising the platform, cookie consent, privacy policy, contacts, and the pages that come with Identity (e.g., login, register, manage account).
Authentication for the Razor Pages application is performed in the standard Identity way.
An Angular SPA, that is only accessible after the user is logged in.
OIDC configuration with Identity Server in order to add authentication and authorisation to the Angular SPA.
All of these three components (Razor Pages + Angular + Identity Server) are bundled into one single .NET Core web project. I have also scaffolded Identity so that I am able to customise the look and behaviour of the pages.
I was able to almost configure the application the way I want it, by basically mixing the code of the startup templates of the Razor Pages option (with user accounts stored locally) and the Angular template option (with user accounts stored locally) and with a bit of trial and error and investigation.
The current status of my application is:
The user logs in in the Razor Pages application.
The login is successful and the email is displayed on the navigation bar.
When we navigate to the SPA, my Angular app tries to silently login and is successful:
localhost:5001/Dashboard (Angular SPA home route)
If we navigate to a part of the Razor Pages application that does not have the /Identity route (which is only used for the pages that come with Identity) the cookies appear to no longer contain the right information and I have no session in those routes. This means that, for example, if I am using the SignInManager.IsSignedIn(User) to only display a navigation option to an Administration page that is protected with an options.Conventions.AuthorizePage($"/Administration"), if I am in a URL that has the Identity route, the navigation tab will be displayed, otherwise it will not be displayed:
localhost:5001/Identity/Account/Login
localhost:5001 (Razor Pages application home route)
However, even though the Administration navigation tab is being displayed when I am on a URL that has the /Identity route, if I click on it I will get a 401 unauthorised error, because the Administration page is not preceded by the /Identity route:
localhost:5001/Administration
I have managed to trace the problem to the the AddIdentityServerJwt(). Without this, the login for the Razor Pages application works as intended, but I am obviously unable to use authentication and authorisation with the Angular application afterwards.
I went to check the source code for that method and it turns out that it creates a new IdentityServerJwtPolicySchemeForwardSelector that forwards the JWT policy scheme to the DefaultIdentityUIPathPrefix which, as you might have guessed it, contains only the value "/Identity".
I have configured my Startup class in the following way:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services
.AddDbContext<ApplicationDbContext>(optionsBuilder =>
{
DatabaseProviderFactory
.CreateDatabaseProvider(configuration, optionsBuilder);
});
services
.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services
.AddIdentityServer()
.AddApiAuthorization<IdentityUser, ApplicationDbContext>();
services
.AddAuthentication()
.AddIdentityServerJwt();
services
.AddControllersWithViews();
services
.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage($"/Administration");
});
services
.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddTransient<IEmailSender, EmailSenderService>();
services.Configure<AuthMessageSenderOptions>(configuration);
services.AddTransient<IProfileService, ProfileService>();
}
public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironment webHostEnvironment)
{
SeedData.SeedDatabase(applicationBuilder, configuration);
if (webHostEnvironment.IsDevelopment())
{
applicationBuilder.UseDeveloperExceptionPage();
applicationBuilder.UseDatabaseErrorPage();
}
else
{
applicationBuilder.UseExceptionHandler("/Error");
applicationBuilder.UseHsts();
}
applicationBuilder.UseHttpsRedirection();
applicationBuilder.UseStaticFiles();
applicationBuilder.UseCookiePolicy();
if (!webHostEnvironment.IsDevelopment())
{
applicationBuilder.UseSpaStaticFiles();
}
applicationBuilder.UseRouting();
applicationBuilder.UseAuthentication();
applicationBuilder.UseIdentityServer();
applicationBuilder.UseAuthorization();
applicationBuilder.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
applicationBuilder.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (webHostEnvironment.IsDevelopment())
{
if (bool.Parse(configuration["DevelopmentConfigurations:UseProxyToSpaDevelopmentServer"]))
{
spa.UseProxyToSpaDevelopmentServer(configuration["DevelopmentConfigurations:ProxyToSpaDevelopmentServerAddress"]);
}
else
{
spa.UseAngularCliServer(npmScript: configuration["DevelopmentConfigurations:AngularCliServerNpmScript"]);
}
}
});
}
How can I configure my application so that the session is available across my entire application and not just on URLs that have the "/Identity" route while maintaining both authentication and authorisation for the Razor Pages application and the Angular application?
I had the same problem and solved it by adding my own PolicyScheme that decides which type of authentication should be used based on the request path. All my razor pages have a path starting with "/Identity" or "/Server" and all other requests should use JWT.
I set this up in ConfigureServices using the collowing coding:
// Add authentication using JWT and add a policy scheme to decide which type of authentication should be used
services.AddAuthentication()
.AddIdentityServerJwt()
.AddPolicyScheme("ApplicationDefinedAuthentication", null, options =>
{
options.ForwardDefaultSelector = (context) =>
{
if (context.Request.Path.StartsWithSegments(new PathString("/Identity"), StringComparison.OrdinalIgnoreCase) ||
context.Request.Path.StartsWithSegments(new PathString("/Server"), StringComparison.OrdinalIgnoreCase))
return IdentityConstants.ApplicationScheme;
else
return IdentityServerJwtConstants.IdentityServerJwtBearerScheme;
};
});
// Use own policy scheme instead of default policy scheme that was set in method AddIdentityServerJwt
services.Configure<AuthenticationOptions>(options => options.DefaultScheme = "ApplicationDefinedAuthentication");