With identityserver4, read, write, delete operations according to the user's authority - api

I am authenticating with identityserver4. I am using database sql server.
I have completed migration processes, user registration and user authorizations.
There is a user I have specified and I have not given the user read permission.
I reported this to the API side, but it can read even if the user has no authorization.
How can I fix this?
[Authorize(Policy = "ReadPurchasingManagement")]
I coded the config file as follows
Config.cs
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
using System;
using System.Collections.Generic;
namespace IdentityServer
{
public static class Config
{
#region ApiResources
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>() {
new ApiResource("resource_purchasing"){Scopes= {"purchasing.create", "purchasing.read", "purchasing.update", "purchasing.delete"}},
new ApiResource(IdentityServerConstants.LocalApi.ScopeName)
};
}
#endregion
#region Identity Resource
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>()
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
new IdentityResources.Phone(),
new IdentityResources.Address(),
new IdentityResource()
{
Name="roles",
DisplayName="Roles",
Description= "User roles",
UserClaims = { JwtClaimTypes.Role },
}
};
}
#endregion
#region Scopes
public static IEnumerable<ApiScope> GetApiScope()
{
return new List<ApiScope>()
{
#region Purchasing Management Permissions
new ApiScope("purchasing.create", "PM create"),
new ApiScope("purchasing.read", "PM read"),
new ApiScope("purchasing.update", "PM update"),
new ApiScope("purchasing.delete", "PM delete"),
new ApiScope(IdentityServerConstants.LocalApi.ScopeName),
#endregion
};
}
#endregion
#region Clients
public static IEnumerable<Client> GetClients()
{
return new List<Client>()
{
new Client()
{
ClientId = "PurchasingClient",
ClientSecrets = new[] { new Secret("secret".Sha256())},
ClientName = "Purchasing Management",
AllowedGrantTypes= GrantTypes.ResourceOwnerPasswordAndClientCredentials,
Enabled = true,
AccessTokenType = AccessTokenType.Jwt,
AlwaysSendClientClaims = true,
UpdateAccessTokenClaimsOnRefresh = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowAccessTokensViaBrowser = true,
IncludeJwtId = true,
AllowedScopes = {
IdentityServerConstants.LocalApi.ScopeName,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Address,
IdentityServerConstants.StandardScopes.OfflineAccess,
IdentityServerConstants.LocalApi.ScopeName,
"roles",
"resource_purchasing",
"purchasing.create",
"purchasing.read",
"purchasing.update",
"purchasing.delete"
},
AllowOfflineAccess=true,
AccessTokenLifetime= 4*60*60,
RefreshTokenExpiration=TokenExpiration.Absolute,
AbsoluteRefreshTokenLifetime=(int)(DateTime.Now.AddDays(60)-DateTime.Now).TotalSeconds,
RefreshTokenUsage = TokenUsage.ReUse
}
};
}
#endregion
}
}
Startup.cs
using IdentityServer.Data;
using IdentityServer.Models;
using IdentityServer.Services;
using IdentityServer4;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
namespace IdentityServer
{
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalApiAuthentication();
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("IdentityServerDbConnection")));
#region Identity Roles
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 2;
options.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
#endregion
#region Identity Server Configuration
var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.EmitStaticAudienceClaim = true;
})
.AddConfigurationStore(opts =>
{
opts.ConfigureDbContext = c => c.UseSqlServer(Configuration.GetConnectionString("IdentityServerDbConnection"), sqlOptions => sqlOptions.MigrationsAssembly(assemblyName));
})
.AddOperationalStore(opts => (refresh token, authorization code)
{
opts.ConfigureDbContext = c => c.UseSqlServer(Configuration.GetConnectionString("IdentityServerDbConnection"), sqlOptions => sqlOptions.MigrationsAssembly(assemblyName));
})
.AddDeveloperSigningCredential()
.AddAspNetIdentity<ApplicationUser>()
.AddResourceOwnerValidator<IdentityResourceOwnerPasswordValidator>();
#endregion
services.AddAuthentication()
.AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "DynamicBoxWorkflow";
options.ClientSecret = "secret";
});
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
}
I made a definition like this on the client API side. But the user does not have permission, but he can read even though he is not authorized.
Client API
#region Identity Server 4 -> JWT Auth
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = builder.Configuration["IdentityServer:Authority"];
options.Audience = "resource_purchasing";
options.RequireHttpsMetadata = false;
});
#region Authorization - Claims
builder.Services.AddAuthorization(opts =>
{
opts.AddPolicy("CreatePurchasingManagement", policy =>
{
policy.RequireClaim("scope", new[] { "purchasing.create" });
});
opts.AddPolicy("ReadPurchasingManagement", policy =>
{
policy.RequireClaim("scope", new[] { "purchasing.read" });
});
opts.AddPolicy("UpdatePurchasingManagement", policy =>
{
policy.RequireClaim("scope", new[] { "purchasing.update" });
});
opts.AddPolicy("DeletePurchasingManagement", policy =>
{
policy.RequireClaim("scope", new[] { "purchasing.delete" });
});
});
#endregion

Related

Htttp 400 Bad Request Request Header is too long

I am currently developing an application using Asp.net core 5.0 and Identity server 4.My OIDC authentication flow handled by Microsoft.AspNetCore.Authentication.OpenIdConnect. I deployed my application into IIS and I am getting my login screen. But after login I got the Http 400 Bad request error.Error. I checked my application cookie it contains many AspNetCore.OpenIdConnect.Nonce cookies. I deleted the cookies but doesn't solve my issue. What is the proper solution to handle this solution. I tried this but doesn't help me. I will share the code below
MVC Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddScoped<RenewToken>();
services.AddAuthorization(options =>
{
options.AddPolicy("CreatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RC", "UC")));
options.AddPolicy("ReadPolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RR", "UR")));
});
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.Cookie.Name = "Cookies";
options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
options.SlidingExpiration = true;
}).AddOpenIdConnect("oidc", options =>
{
options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = delegate { return true; } };
options.Authority = Configuration.GetSection("API:IDS4").Value;
options.SignInScheme = "Cookies";
options.SignedOutRedirectUri = Configuration.GetSection("API:WebClient").Value + "/signout-callback-oidc";
options.RequireHttpsMetadata = true;
options.ClientId = "mvc";
options.ClientSecret = "*****";
options.ResponseType = "code";
options.UsePkce = true;
options.Scope.Add("profile");
options.Scope.Add("mcApi");
options.Scope.Add("Api1");
options.Scope.Add("Api2");
options.Scope.Add("Api3");
options.Scope.Add("Api4");
options.Scope.Add("Api5");
options.Scope.Add("Api6");
options.Scope.Add("Api7");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events.OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Prompt = "login";
return Task.CompletedTask;
};
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = (context) =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = JwtClaimTypes.Role
};
});
services.AddHttpClient<IAdminService, AdminService>();
services.AddSingleton<DataProtectionPurposeStrings>();
services.AddSingleton<GlobalConstants>();
}
private bool AuthorizeAccess(AuthorizationHandlerContext context, string roleClaim, string userClaim)
{
return context.User.HasClaim(claim => claim.Type == roleClaim && claim.Value == "True") &&
context.User.HasClaim(claim => claim.Type == userClaim && claim.Value == "True") ||
context.User.IsInRole("MyAdmin");
}
// 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("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Identity Server Startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
// 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.AddControllersWithViews();
/****Register asp.net core Identity DBConetexts***/
var idenConnectionString = Configuration["DbContextSettings:IdentityConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var builder = new NpgsqlConnectionStringBuilder(idenConnectionString)
{
Password = dbPassword
};
services.AddDbContext<IdentityDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequiredLength = 8;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+ ";
options.SignIn.RequireConfirmedEmail = false;
options.User.RequireUniqueEmail = false;
}).AddRoles<ApplicationRole>().AddEntityFrameworkStores<IdentityDBContext>()
.AddDefaultTokenProviders();
/****Identity Server implementation with asp.net core Identity***/
var idsServerConnectionString = Configuration["DbContextSettings:IdentityServer4ConnectionString"];
var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var idsServerdbPassword = Configuration["DbContextSettings:DbPassword"];
var idsServerbuilder = new NpgsqlConnectionStringBuilder(idsServerConnectionString)
{
Password = dbPassword
};
var idBuilder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Login";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromMinutes(10),
CookieSlidingExpiration = true
};
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
options.EnableTokenCleanup = true;
}).AddAspNetIdentity<MembershipUser>()
.AddProfileService<ProfileService>();
X509Certificate2 cert = null;
using (var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
"thumbprint",
false);
if (certCollection.Count > 0)
{
cert = certCollection[0];
}
}
if (Environment.IsDevelopment())
{
idBuilder.AddDeveloperSigningCredential();
}
else
{
idBuilder.AddSigningCredential(cert);
}
idBuilder.Services.ConfigureExternalCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1);
});
idBuilder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1);
});
services.AddMediatR(typeof(Startup));
RegisterServices(services);
}
private void RegisterServices(IServiceCollection services)
{
services.AddSingleton<IEventBus, RabbitMQBus>(sp =>
{
var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>();
return new RabbitMQBus(sp.GetService<IMediator>(), scopeFactory);
});
services.AddTransient<UserDBContext>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to add MVC
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
Can I store the cookies into SessionStore using MemoryCacheTicketStore ? Kindly share your thoughts.
A potential thing could be that IIS thinks the cookie header is too long.
By default ASP.NET Core chunks up the cookie in 4Kb chunks, like this picture shows:
So either you try to reduce the size of the cookie or look at the IIS settings, if you can increase the max header length?
Alternatively, you stop saving the tokens inside the cookie, by setting:
options.SaveTokens = false;
Now you of course need top store it somewhere else, like in a tokenstore.

Identityserver4 not authorized api in blazor client

I use identity server 4 with blazor server side client
everything is ok but token not authorized api methods but token works in server authorized controllers
is something wrong with grant type or code flow ?
server config class :
public static class Configurations
{
public static IEnumerable<IdentityResource> GetIdentityResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource> {
new ApiResource("api1")
};
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// backward compat
new ApiScope("api1")
};
}
public static IEnumerable<Client> GetClients() => new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = {
"api1" ,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
RedirectUris = { "https://localhost:44372/signin-oidc" },
AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess = true,
RequireConsent = false,
RequirePkce = true,
}
};
}
server start up class :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(config =>
{
config.UseInMemoryDatabase("Memory");
});
// AddIdentity registers the services
services.AddIdentity<IdentityUser, IdentityRole>(config =>
{
config.Password.RequiredLength = 4;
config.Password.RequireDigit = false;
config.Password.RequireNonAlphanumeric = false;
config.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "IdentityServer.Cookie";
config.LoginPath = "/Auth/Login";
config.LogoutPath = "/Auth/Logout";
});
services.AddIdentityServer()
.AddAspNetIdentity<IdentityUser>()
//.AddInMemoryApiResources(Configurations.GetApis())
.AddInMemoryIdentityResources(Configurations.GetIdentityResources())
.AddInMemoryApiScopes(Configurations.GetApiScopes())
.AddInMemoryClients(Configurations.GetClients())
.AddDeveloperSigningCredential();
services.AddControllersWithViews();
}
api start up class :
services.AddAuthentication("Bearer").AddIdentityServerAuthentication(option =>
{
option.Authority = "https://localhost:44313";
option.RequireHttpsMetadata = false;
option.ApiName = "api1";
});
blazor server side start up 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));
});
To fix this issue, you have 2 options:
1- (Recommended) To add the scopes to API resource like this:
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource> {
new ApiResource("api1")
{
Scopes = new []{ "api1" }
}
};
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// backward compat
new ApiScope("api1")
};
}
2- On API change your code to set ValidateAudience = false, like this:
services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
options =>
{
options.Authority = "http://localhost:5000";
options.Audience = "api1";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
});
Here is my blog about migrating IdentityServer4 to v4 https://nahidfa.com/posts/migrating-identityserver4-to-v4/
I have not actually used AddIdentityServerAuthentication in API but can you try the below code. Technically its same thing, but maybe this will work.
Change your api authentication from AddIdentityServerAuthentication to AddJwtBearer:
services.AddAuthentication("Bearer").AddIdentityServerAuthentication(option =>
{
option.Authority = "https://localhost:44313";
option.RequireHttpsMetadata = false;
option.ApiName = "api1";
});
to
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", option =>
{
option.Authority = "https://localhost:44313";
option.Audience = "api1";
option.SaveToken = true;
});

Configure blazor wasm and separate identityserver4

I have a solution named Messenger which contains 3 projects:
Messenger.IdentityServer
Messenger.Api
Messenger.BlazorWasmClient
This is my IdentityServer Startup.cs:
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// configures IIS out-of-proc settings (see https://github.com/aspnet/AspNetCore/issues/14882)
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
// configures IIS in-proc settings
services.Configure<IISServerOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
services.AddDbContext<IdentityServerDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityServerUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityServerDbContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<IdentityServerUser>();
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
Config.cs:
public static class Config
{
public static IEnumerable<IdentityResource> Ids =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
// let's include the role claim in the profile
new ProfileWithRoleIdentityResource(),
new IdentityResources.Email()
};
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{
// the api requires the role claim
new ApiResource("messenger.api", "Messenger API", new[] { JwtClaimTypes.Role })
};
// machine to machine client (from quickstart 1)
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "messenger.blazorwasmclient",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowedCorsOrigins = { "https://localhost:5001" },
AllowedScopes = { "openid", "profile", "email", "messenger.api" },
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/" },
Enabled = true
}
};
}
In the BlazorWasmClient, I added an AddOidcAuthentication method to the Program.cs like this:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services
.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
options.UserOptions.RoleClaim = "role";
})
.AddAccountClaimsPrincipalFactory<ArrayClaimsPrincipalFactory<RemoteUserAccount>>();
await builder.Build().RunAsync();
}
}
appSettings.json:
{
"oidc": {
"Authority": "https://localhost:5000/",
"ClientId": "messenger.blazorwasmclient",
"DefaultScopes": [
"openid",
"profile",
"email",
"messenger.api"
],
"PostLogoutRedirectUri": "/",
"ResponseType": "code"
}
}
But I don't understand why I have this error message every time I start my solution:
You need to configure the redirect URI and the PostLogoutUri must be an abosolut URI, not relative. Uris must exactly match what you setup in the IndentityServer4 client :
appsettings.json
{
"oidc": {
"Authority": "https://localhost:5000/",
"ClientId": "messenger.blazorwasmclient",
"DefaultScopes": [
"openid",
"profile",
"email",
"messenger.api"
],
"PostLogoutRedirectUri": "https://localhost:5001/",
"RedirectUri": "https://localhost:5001/authentication/login-callback",
"ResponseType": "code"
}
}

ASP.NET Core returns InternalServerError while using Identity server

I trying to add identity server for my web API as its identity server4 documentation. when I was trying to call API from my console application it's every time returns InternalServerError.
Here is My Identity server Config.cs
public static class Config
{
// register api
public static IEnumerable<ApiScope> ApiScopes => new List<ApiScope>
{
// in here add your api name
new ApiScope("api1", "My API")
};
// register client which is going to access api. eg: front-end application, mobile apps etc. can add multiple client.
public static IEnumerable<Client> Clients => new List<Client>
{
new Client
{
// which is going to access
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
}
and here the identity server startup file configuration service and configure functions
public void ConfigureServices(IServiceCollection services)
{
// uncomment, if you want to add an MVC-based UI
services.AddControllersWithViews();
var builder = services.AddIdentityServer()
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients);
builder.AddDeveloperSigningCredential();
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to add MVC
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
// uncomment, if you want to add MVC
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
and here is my API startup file's congurationService and configure functions
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:14030/";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
}
);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
here is my API controller
[Route("identity")]
public class IdentityController : ControllerBase
{
[HttpGet]
[Authorize]
public IActionResult Get() => Ok(new JsonResult(from c in User.Claims select new { c.Type, c.Value }));
}
and here is my console application client request a api
static async System.Threading.Tasks.Task Main(string[] args)
{
// discover endpoints from metadata
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:14030");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
Scope = "api1"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
Console.WriteLine("\n\n");
// call api
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("https://localhost:5001/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
what are the mistakes should I have to fix. Im really appreciates your valuable answers and efforts.
Thank
I got the code working, I would do the following:
use HTTPS here, not HTTP:
var disco = await
client.GetDiscoveryDocumentAsync("http://localhost:14030");
Remove the duplicate lines of in IdentityServer startup class:
builder.AddDeveloperSigningCredential();
I would add in your API startup.cs
services.AddAuthorization();
Remove the trailing / at the end of the URL here:
options.Authority = "https://localhost:14030/";
To get more debugging output from your API, you can add the following two trace lines to your appsettings.Development.json file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.Authentication": "Trace",
"Microsoft.AspNetCore.Authorization": "Trace"
}
}
}
If you want to validate the Audience (and using IdentityServer4 v4.00) you can add:
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:14030";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new[] {"https://localhost:14030/resources"},
ValidateAudience = true
};
}
);

Identityserver4, problem with running and implementation

I want to create a centralize asp.net core API project with Entity-FrameWork core for membership management such as login, register and etc. and Separately I want to create another asp.net core project and use centralized project for membership like google.com.
After a lot of search, i understood that should use IdentityServer4. I read this document and got sample from Github, but it was not clear and I was confused.
Who can explain clearly and step by step?
thank you
IdentityServer4 has simple MeadleWare for use in Asp.Net Core
public void ConfigureServices(IServiceCollection services){
...
var cert = new X509Certificate2("/Cert/cert.pfx", "123456");
services.AddIdentityServer()
.AddInMemoryApiResources(Config.GetApisResources())
.AddSigningCredential(cert)
.AddInMemoryClients(Config.GetClients())
.Services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
...
}
public void Configure(ILoggerFactory loggerFactory, IApplicationBuilder app, IHostingEnvironment env){
...
app.UseIdentityServer();
...
}
and create a config file:
public class Config
{
public static IEnumerable<ApiResource> GetApisResources()
{
return new[]
{
// simple API with a single scope (in this case the scope name is the same as the api name)
new ApiResource("api1"),
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "spa",
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
//IdentityTokenLifetime=10,
AllowOfflineAccess=true,
RefreshTokenExpiration = TokenExpiration.Absolute,
AbsoluteRefreshTokenLifetime = 999999,
RefreshTokenUsage=TokenUsage.ReUse,
AccessTokenType=AccessTokenType.Jwt,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes =
{
"api1",
IdentityServerConstants.StandardScopes.OfflineAccess
},
AccessTokenLifetime=36000
}
};
}
}
then in resourceServer use Bellow MW:
public void ConfigureServices(IServiceCollection services){
...
services.AddAuthentication(o =>
{
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = "http://localhost:5000";
o.Audience = "self";
o.RequireHttpsMetadata = false;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero
};
o.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = c =>
{
c.NoResult();
c.Response.StatusCode = 401;
c.Response.ContentType = "text/plain";
return c.Response.WriteAsync(c.Exception.ToString());
},
OnTokenValidated = context =>
{
return Task.CompletedTask;
},
OnMessageReceived = context =>
{
return Task.CompletedTask;
},
OnChallenge = context =>
{
return Task.CompletedTask;
}
};
});
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseAuthentication();
...
}