How to make IdentityServer client works with any origin? - identityserver4

When I create a new client in IdentityServer, I can set AllowedCorsOrigins to list of urls,but is there a way to make it work with any origin without setting specific urls?
I searched and if I'm correct,it seems custom implementation of ICorsPolicyService is the way, not sure if this is the only way,but if it's then how can I register it in identity server? I created the class but I
m not sure how to go further to make it work with identity server?

In your ConfigureServices method of your startup, you can set up Cors something like this:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
Note that this is allowing any origin, but also any header/method ... you may want to change this for your own use-case.
Then in the Configure of your startup make sure you actually use your policy, like this:
app.UseCors("CorsPolicy");

Related

Authorization on a Blazor Server app using Microsoft Identity (Azure AD Authentication) doesn't work

Forgive me if this is an obvious question with an easy answer but for the life of me I cannot get my app to behave the way I want.
When I normally use MS Identity in my Blazor apps I can create roles and policies which all come from a SQL database. For this B2B app I need to use Azure AD and the groups within there to authenticate and authorize access.
At the moment the whole app is secured because the default policy is being applied to all parts of the site but I would like to use [Authorize(Policy = "ViewCustomer")] for example to ensure users have the right permission to view a particular part.
I am sure that this part of my program.cs is missing policies and is part of the problem:
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
Trouble is I don't have a clue how to create these so they refer to groups (or similar) in the Azure AD tenant. My complete program.cs is:
using DevExpress.Blazor;
using DataModel.Models;
using Microsoft.EntityFrameworkCore;
using BlazorUI.Hubs;
using BlazorUI.Services;
using Xero.NetStandard.OAuth2.Config;
using BlazorUI.Services.Interfaces;
using DataModel.Xero.Interface;
using DataModel.DickerData.Interfaces;
using DataModel.DickerData;
using DataModel.Xero;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
var builder = WebApplication.CreateBuilder(args);
var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' ') ?? builder.Configuration["MicrosoftGraph:Scopes"]?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddScoped<ISettingService, SettingService>();
builder.Services.AddScoped<IXeroService, XeroService>();
builder.Services.AddScoped<IDickerDataService, DickerDataService>();
//XERO SETTINGS
builder.Services.Configure<XeroConfiguration>(builder.Configuration.GetSection("XeroConfiguration"));
//DICKER DATA SETTINGS
builder.Services.Configure<DickerConfig>(builder.Configuration.GetSection("DickerDataConfiguration"));
//DEVEXPRESS
builder.Services.AddDevExpressBlazor(configure => configure.BootstrapVersion = BootstrapVersion.v5);
//ENTITY FRAMEWORK
builder.Services.AddDbContextFactory<ApplicationDBContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DBConnection"));
options.EnableSensitiveDataLogging();
});
var app = builder.Build();
//DEVEXPRESS
builder.WebHost.UseWebRoot("wwwroot");
builder.WebHost.UseStaticWebAssets();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
//REGISTER SIGNAL R HUBS
app.MapHub<MessageHub>(MessageHub.PATHTOHUB);
app.Run();
Thank you so much to anyone that may be able to enlighten me.
You can add app roles to your application, assign them to selected users and obtain them as claims so they can be used in tandem with your AuthorizeAttribute.
Alternatively, you can further customize or augment the authorization criteria using Claim Based Authorization in tandem with your policy.

.NET Core 3.1 to .NET 5 migration - Microsoft Identity Web Platform stopped working

With NET 5 officially released, this evening I've migrated from Net Core 3.1 to NET 5, all seemed to go smoothly until I tried to run the app and now find a couple of squiggly lines under two items in the startup.cs that are associated with Microsoft Identity Web platform. This is obviously an instant fail! I wont be able to fire up the app or login to Azure AD until I have this fixed.
After modifying the csproj file to NET5, I then went to nuget manager and updated all the packages.
I've absolutely no idea where to start on this issue :(
Screenshot of the startup.cs file with the squiggles:
csproj file:
Nuget Manager with updated packages:
I notice that the package references at the top of the startup.cs file for MS Identity Web are now greyed out since the migration:
Code from the startup.cs file:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public TokenValidatedContext Context { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Added to original .net core template.
// ASP.NET Core apps access the HttpContext through the IHttpContextAccessor interface and
// its default implementation HttpContextAccessor. It's only necessary to use IHttpContextAccessor
// when you need access to the HttpContext inside a service.
// Example usage - we're using this to retrieve the details of the currrently logged in user in page model actions.
services.AddHttpContextAccessor();
// DO NOT DELETE (for now...)
// This 'Microsoft.AspNetCore.Authentication.AzureAD.UI' library was originally used for Azure Ad authentication
// before we implemented the newer Microsoft.Identity.Web and Microsoft.Identity.Web.UI NuGet packages.
// Note after implememting the newer library for authetication, we had to modify the _LoginPartial.cshtml file.
//services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
// .AddAzureAD(options => Configuration.Bind("AzureAd", options));
///////////////////////////////////
// Add services required for using options.
// e.g used for calling Graph Api from WebOptions class, from config file.
services.AddOptions();
// Sign-in users with the Microsoft identity platform
services.AddSignIn(Configuration);
// Token acquisition service based on MSAL.NET
// and chosen token cache implementation
services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { GraphScopes.UserRead })
.AddInMemoryTokenCaches();
// Add the MS Graph SDK Client as a service for Dependancy Injection.
services.AddGraphService(Configuration);
// Create a new instance of the class that stores the methods called
// by OpenIdConnectEvents(); i.e. when a user logs in or out the app.
// See section below :- 'services.Configure'
OpenIdEvents openIdEvents = new OpenIdEvents();
// The following lines code instruct the asp.net core middleware to use the data in the "roles" claim in the Authorize attribute and User.IsInrole()
// See https://learn.microsoft.com/aspnet/core/security/authorization/roles?view=aspnetcore-2.2 for more info.
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
// The claim in the Jwt token where App roles are available.
options.TokenValidationParameters.RoleClaimType = "roles";
// Advanced config - capturing user events. See OpenIdEvents class.
options.Events ??= new OpenIdConnectEvents();
options.Events.OnTokenValidated += openIdEvents.OnTokenValidatedFunc;
// This is event is fired when the user is redirected to the MS Signout Page (before they've physically signed out)
options.Events.OnRedirectToIdentityProviderForSignOut += openIdEvents.OnRedirectToIdentityProviderForSignOutFunc;
// DO NOT DELETE - May use in the future.
// OnSignedOutCallbackRedirect doesn't produce any user claims to read from for the user after they have signed out.
options.Events.OnSignedOutCallbackRedirect += openIdEvents.OnSignedOutCallbackRedirectFunc;
});
// Adding authorization policies that enforce authorization using Azure AD roles. Polices defined in seperate classes.
services.AddAuthorization(options =>
{
// This line may not work for razor at all, havent tried it but what was used in MVC from the MS Project example. Dont delete just yet...
//options.AddPolicy(AuthorizationPolicies.AssignmentToUserReaderRoleRequired, policy => policy.RequireRole(AppRole.UserReaders));
// NOTE BELOW - I had to change the syntax from RequireRole to RequireClaim
options.AddPolicy(AuthorizationPolicies.AssignmentToEditRolesRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.EditRoles));
options.AddPolicy(AuthorizationPolicies.AssignmentToViewLogsRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.ViewLogs));
options.AddPolicy(AuthorizationPolicies.AssignmentToViewUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.ViewUsers));
options.AddPolicy(AuthorizationPolicies.AssignmentToCreateUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.CreateUsers));
options.AddPolicy(AuthorizationPolicies.AssignmentToEditUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.EditUsers));
options.AddPolicy(AuthorizationPolicies.AssignmentToDeleteUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.DeleteUsers));
});
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
// Add the HttpClient factory into our dependancy injection system.
// That way we can access it at any point.
// Used for consuming REST Api throughout the Webb App.
services.AddHttpClient();
// Adds the service for creating the Jwt Token used for calling microservices.
// Note we are using our independant bearer token issuer service here, NOT Azure AD
services.AddScoped<JwtService>();
// Add service for HttpContext Current User Repository.
// Used fir fetching properties of the currently logged in user for logging.
services.AddScoped<ICurrentUser, CurrentUser>();
// The AddAntiforgery() method configures anti-forgery service to pick the anti-forgery
// token from request headers rather than request body. This is required because we will
// be issuing Ajax requests to the razor page and there won't be any full page post-backs.
services.AddAntiforgery(options => options.HeaderName = "MY-XSRF-TOKEN");
}
I just don't know how to troubleshoot this...
OK, got it working. The first issue I got the answer from another post:
The services.AddSignIn() is available in the nuget package of Microsoft.Identity.Web up to version 0.1.5 Preview version, the above versions don't contain the services.AddSignIn()
In my case I'm using Microsoft.Identity.Web ver 1.3.0
I replaced the code shown directly below with the code section at the bottom:
Old Code:
// Sign-in users with the Microsoft identity platform
services.AddSignIn(Configuration);
// Token acquisition service based on MSAL.NET
// and chosen token cache implementation
services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { GraphScopes.UserRead })
.AddInMemoryTokenCaches();
Replaced the above with the following code:
// Sign-in users with the Microsoft identity platform
//services.AddSignIn(Configuration);
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
// Token acquisition service based on MSAL.NET and chosen token cache implementation
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { GraphScopes.UserRead })
.AddInMemoryTokenCaches();
I've run some quick checks to ensure that I'm able to perform changes to a user profile which is using the MS Graph so I'm comfortable enough this solved my issue.

Co-hosting Identity Server 4 with API services using Roles

I've come across an example of co-hosting Identity Server 4 on the same App Host as the API Services that need it for authentication and authorization.
Now I was able to replicate this successfully by just pure authentication but when it comes to authorization using Roles I couldn't get it to work, i.e. adding the [Authorize(Roles = "My Role")] attribute on my Controller Action.
The access token contains the "role" scope and claim but it doesn't seem to be respected at all.
I initially tried the code below but it doesn't execute the JWT Bearer bit at all which leads me to believe that Identity Server uses its own handler for that purpose and I have no idea how to configure it if at all.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
// JWT tokens (default scheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:44367/";
options.Audience = "API";
options.TokenValidationParameters.RoleClaimType = "role";
});
Then I came across this line from the example code (I mentioned initially in this post) which seems like its supposed to grant me the ability to run API services along side with Identity Server:
services.AddLocalApiAuthentication();
But it also doesn't seem to do what I want.
So does the Identity Server authentication middleware allow me to accomplish role-based authentication or is there some other mechanism (i.e. Policies) that I need to look into?
Something worth noting, I was able to accomplish all of this successfully with Identity Server 4 but by hosting it all separately. I want to see what it takes to host it all together.
Just rechecked the example and it worked perfectly fine.
When you use
services.AddLocalApiAuthentication();
it sets up the IdentityServerAccessToken authenticationScheme.
To use it in your API controller you type
[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
as described in the doc, or just
[Authorize(AuthenticationSchemes = "IdentityServerAccessToken")]
All you need to check the roles is one more argument for the attribute:
[Authorize(AuthenticationSchemes = "IdentityServerAccessToken", Roles = "test role1")]

How do you create a API/IdentityServer/Blazor(server-side) application?

I attempted to build this application myself but, have hit several stumbling blocks along the way. I am thinking that it may be best to step back and take a larger look at what I am trying to create. There doesn't seem to be any documentation on how to make what I am looking for. (unless someone can point me in the right place I might have missed)
Ultimately what I would like is to have a Blazor(server-side) application make API calls to use data in the app and then have an IdentityServer4 encapsulate the authentication. I need to have Azure as well as ASP.net Identity as the possible authentication methods.
I have tried and was able to create an IdentityServer4 that also has a local API. I can make calls to this from Postman to get token and such. But, when it comes to tying a Blazor(server-side) application to the IdentityServer4 I am befuddled.
I have tried to ask this question in specifics but, haven't gotten any results at all. I am hoping maybe this larger look at it might be helpful.
It seems like odic-client.js is the way to get the data from the IdentityServer4 callback but, that doesn't seem to tie in nicely with the .NET Authorization in Blazor(server-side). How do I get these to work together.
IMPORTANT: There are better sources now than my answer. Follow the links provided in the last part of this answer.
I've got a similar setup with API / IdentityServer4 / Blazor(server-side). I'll show you some of the code I used, maybe you can make some use of it.
Using the NuGet Package Microsoft.AspNetCore.Authentication.OpenIdConnect, I've got this code in the ConfigureServices method in the Startup class:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "myClient";
options.ClientSecret = "mySecret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("MyApi");
options.Scope.Add("offline_access");
options.ClaimActions.MapJsonKey("website", "website");
});
and in the Configure method app.UseAuthentication();
Then in App.razor i used the CascadingAuthenticationState component:
<CascadingAuthenticationState>
<Router AppAssembly="typeof(Startup).Assembly" />
</CascadingAuthenticationState>
And using the NuGet package Microsoft.AspNetCore.Authorization in my main page Index.razor:
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize]
Now it should say "Not authenticated" when you open the main page but there's still no redirection to the IdentityServer4. For this you've got to add MVC in the startup too, as I learned from this stackoverflow question:
services.AddMvcCore(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Now you should be getting redirected to IdentityServer4 to log in after starting the application. In my case I've got an ApiClient, which describes the methods of my API. I use DI to inject the ApiClient and add the access token:
services.AddHttpClient<IApiClient, ApiClient>(async (serviceProvider, client) =>
{
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.BaseAddress = new Uri("http://localhost:55578");
});
Like you said, there is not much documentation on this topic except some answers here on stackoverflow. It took me a long time to set this up, so I hope I can help someone else with this post.
UPDATE: Logout process
Logging out with this setup requires a detour to a razor page because the HttpContext is inaccessible after the blazor component is loaded.
Create a new Razor Page in the Pages folder and add the following code to the newly created Logout.cshtml.cs:
public class LogoutModel : PageModel
{
public async void OnGetAsync()
{
await HttpContext.SignOutAsync("Cookies");
var prop = new AuthenticationProperties()
{
RedirectUri = "http://localhost:62909"
};
await HttpContext.SignOutAsync("oidc", prop);
}
}
Add a logout button somewhere which calls the function UriHelper.NavigateTo("/Logout") relying on #inject IUriHelper UriHelper. Done!
UPDATE: Login Workaround
The previously described login process worked locally but after publishing to the test server, I had the problem, that the IHttpContextAccessor was always null inside the AddHttpClient method. So I ended up using the same workaround as with the logout process. I let the IdentityServer redirect to a razor page (which always has a HttpContext), save the access token in the user claim and redirect to the index page. In the AddHttpClient method I only get the token from the user claim and put it into the authentication header.
UPDATE: Open issues
I still struggle to get this setup working on our server. I opened this issue and requirement on the AspNetCore Github but both got closed without a proper answer. For the time being, I found some blogs that give a good overview of the current state of the topic:
https://mcguirev10.com/2019/12/15/blazor-authentication-with-openid-connect.html
https://wellsb.com/csharp/aspnet/blazor-consume-identityserver4-protected-api/
Try this
Blazor Consume IdentityServer4 Protected API

Protect a single api resource with multiple IDServers

So I have a .Net Core web api, lets call it "CMS" and its currently protected by an IdentityServer4 server as an api resource. I have configured the ID4 server to have the IDP Claim of MyIDP.
For business reasons, I need to give a client their own IdentityServer but they would also like to have their users access the same api "CMS" .
Is this possible?
In the StartUp.cs of my CMS api it currently looks like this
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://www.idserver1.com";
options.RequireHttpsMetadata = true;
options.ApiName = "cmsapi";
});
so to add protection for another id server I assume i could just duplicate the AddAuthentication but change the scheme name from Bearer to something else but that seems wrong?
The reason I think this should be possible because I have been able to add multiple external providers to my Web Application in this manner . But this is for s sign in flow and not for an api.
If this is possible how do I go about this?
This can be achieved quite simply. Suppose you want to issue a separate subdomain for each of your clients: auth0.yourdomain.com, auth1.yourdomain.com and you want an api resource to respect the token from either of those identity providers.
Assuming that the signing key is the same, you can configure a shared issuer uri on the identity server side in Startup.cs->ConfigureServices(...):
var builder = services.AddIdentityServer(options => {
options.IssuerUri = "auth.yourdomain.com";
})
...
And then on the api side you can respect the single issuer uri without having to duplicate authentication schemes:
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "auth.yourdomain.com";
options.RequireHttpsMetadata = true;
options.ApiName = "cmsapi";
});
One thing I can't remember is if the request scheme (http/https) is inferred for the issuer uri or not so you might need to specify that as well (https:\\auth.yourdomain.com). Other than that, this sort of implementation should be quite seamless as far as your clients are concerned.
i think i may have figured out the solution, based off another problem that was happening to me over here
Using Client Credentials flow on identityserver4 and custom AuthorizationHandler User.Identity.isAuthenticated = false
turns out you can use multiple authenticationschemes to protect an api and choose which things to protect with what using the authenticationSchemes property of the Authorize Attribute.
so you would just need a way to map the incoming bearer token to the correct authentication scheme

Resources