IdentityServer4 and Code with PKCE testing with Postman - c#

I'm getting started with IdentityServer4 and I made it through with resource owner flow, but since it's not recommended anymore, PKCE is, I decided to change it. I'm getting the following error message which is obvious because I'm no longer using GrantTypes.ResourceOwnerPassword.
fail: IdentityServer4.Validation.TokenRequestValidator[0]
Client not authorized for resource owner flow, check the AllowedGrantTypes setting{ client_id = trusted }, details: {
"ClientId": "trusted",
"ClientName": "Dayum Client",
"GrantType": "password",
"Raw": {
"grant_type": "password",
"username": "Admin",
"password": "***REDACTED***",
"scope": "openid profile offline_access api1",
"client_id": "trusted"
}
}
I don't find much information, because it's new but how am I supposed to test it with Postman? I used to do the following with resource owner flow:
POST http://localhost:58508/connect/token
grant_type = password
username=Admin
password=123456
scope=openid profile offline_access api1
client_id=trusted
I know I can't do that anymore with Code with PKCE. How do I request an access and refresh tokens, how do I test it with Postman?
Code:
public static class Config
{
public static IEnumerable<IdentityResource> GetResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource>
{
new ApiResource("api1", "My API")
};
public static IEnumerable<Client> GetClients() =>
new List<Client>
{
new Client
{
ClientId = "trusted",
ClientName = "Dayum Client",
//ClientSecrets = { new Secret("xxxxxxxxxxxxxxxxxxxxxxx".Sha256()) },
RequireConsent = false,
RequireClientSecret = false,
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:58508" },
PostLogoutRedirectUris = { "http://localhost:58508" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"api1"
},
AccessTokenType = AccessTokenType.Jwt,
AccessTokenLifetime = 900,
AllowOfflineAccess = true,
RefreshTokenExpiration = TokenExpiration.Absolute,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
AbsoluteRefreshTokenLifetime = 1800
}
};
}
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();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DayumConnection"],
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddSigningCredential(new X509Certificate2(Configuration["Certificates:Default:Path"], Configuration["Certificates:Default:Password"]))
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration["ConnectionStrings:DayumConnection"],
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration["ConnectionStrings:DayumConnection"],
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
options.EnableTokenCleanup = true;
})
.AddProfileService<ProfileService>()
.AddAspNetIdentity<ApplicationUser>();
}
public void Configure(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
context.Database.Migrate();
if (!context.Clients.Any())
{
foreach (var client in Config.GetClients())
{
context.Clients.Add(client.ToEntity());
}
context.SaveChanges();
}
if (!context.IdentityResources.Any())
{
foreach (var resource in Config.GetResources())
{
context.IdentityResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
if (!context.ApiResources.Any())
{
foreach (var resource in Config.GetApis())
{
context.ApiResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
}
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseIdentityServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "MyArea",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}

The first thing is that PKCE support for OAuth 2.0 is available in the latest version of Postman app (v7.23.0) , please update your Postman to the latest version .
Then in Authorization header of Postman , set Type to OAuth2 and click the Get New Access Token button , set Grant Type to Authorization code(With PKCE) and set endpoints/client info as below :
And Auth url/Access Token Url is your identity server 4 's endpoints and replace the Callback url as your client app's redirect url . I notice in your codes , you set the same endpoint/url host(http://localhost:58508) in identity server and client , please modify that based on your real requirements .

Related

Identity Server 4 - Custom Identity Claims are not showing in Id Token, but scopes are visible in Access Token

Requirement: Here I have MVC Client request IdentityServer for Id & access token using Auth-Code mechanism.
Infra: .NetCore = 3.1 & IdentityServer4 = 3.1.4
For this I have used 2 types of configurations.
Type1 - AlwaysIncludeUserClaimsInIdToken = true
Type2 - AlwaysIncludeUserClaimsInIdToken = false & options.ClaimActions.MapJsonKey() to
map the claims. & GetClaimsFromUserInfoEndpoint = true
When you see the code below, I have seggregated as 3 layers, first one is Type1 & sceond one is Type2 and common Config is for both Type1 & Type2(as it doesn't vary).
Common Configuration.
Identity Server Proj::
Program.cs //Add Claims when user is created.
public static void Main(string[] args)
{
//DI is ready when we call create host builder.
var host = CreateHostBuilder(args).Build();
// use DI to inject/seeding of new users.
using (var scope = host.Services.CreateScope())
{
var userManager = scope.ServiceProvider
.GetRequiredService<UserManager<IdentityUser>>();
userManager.CreateAsync(SeedingUsers.GetUser, "password").GetAwaiter().GetResult();
userManager.AddClaimsAsync(SeedingUsers.GetUser, new List<Claim>
{
new Claim("employee_id", "employee_id") //added employee_id as custom claim
}).GetAwaiter().GetResult();
}
host.Run();
}
startp.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(config =>
{
config.UseInMemoryDatabase("Memory");
});
services.AddIdentity<IdentityUser, IdentityRole>(config =>
{
config.Password.RequiredLength = 4;
config.Password.RequireDigit = false;
config.Password.RequireUppercase = false;
config.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "Identity_Cookie"; //It holds logged-in user's data.
config.LoginPath = "/Accounts/Login";
});
services.AddIdentityServer()
.AddAspNetIdentity<IdentityUser>()
.AddInMemoryIdentityResources(MyConfiguration.GetIdentityResources)
.AddInMemoryApiResources(MyConfiguration.GetApiResources)
.AddInMemoryClients(MyConfiguration.GetClients)
.AddDeveloperSigningCredential();
services.AddControllersWithViews();
}
1. Type-1 Configuration.
Identity Server Proj::
MyConfiguration.cs //used to configure Identity.
public static class MyConfiguration
{
public static IEnumerable<Client> GetClients =>
new List<Client>
{
// Registering client for mvc
new Client
{
ClientId = "client_id_mvc",
ClientSecrets = new List<Secret>()
{
new Secret("client_secret_mvc".ToSha256())
},
RedirectUris = { "https://localhost:44362/signin-oidc" },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = {
"Aum",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"custom.employee_profile", //custom Id-Scope
},
AlwaysIncludeUserClaimsInIdToken = true,
RequireConsent = false,
}
};
// This is used to craft the Id Token by IdentityServer.
public static IEnumerable<IdentityResource> GetIdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "custom.employee_profile",
UserClaims = {"employee_id"},
}
};
// This is used to craft the access Token by IdentityServer.
public static IEnumerable<ApiResource> GetApiResources =>
new List<ApiResource> {
new ApiResource("Aum")
};
}
MVC Client Proj::
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(config =>
{
config.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = "oidc";
config.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:44322/";
options.ClientId = "client_id_mvc";
options.ClientSecret = "client_secret_mvc";
options.SaveTokens = true;
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = false; // process -1
//custom claims
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("custom.employee_profile"); //Requesting for custom scope.
});
services.AddControllersWithViews();
}
2. Type-2 Configuration.
Identity Server Proj::
MyConfiguration.cs //used to configure Identity.
public static class MyConfiguration
{
public static IEnumerable<Client> GetClients =>
// Registering client for mvc
new Client
{
ClientId = "client_id_mvc",
ClientSecrets = new List<Secret>()
{
new Secret("client_secret_mvc".ToSha256())
},
RedirectUris = { "https://localhost:44362/signin-oidc" },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = {
"Aum",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"custom.employee_profile", //custom Id-Scope
},
AlwaysIncludeUserClaimsInIdToken = false, // process -2
RequireConsent = false,
}
};
// This is used to craft the Id Token by IdentityServer.
public static IEnumerable<IdentityResource> GetIdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "custom.employee_profile",
UserClaims = {"employee_id"},
},
};
// This is used to craft the access Token by IdentityServer.
public static IEnumerable<ApiResource> GetApiResources =>
new List<ApiResource> {
new ApiResource("Aum")
};
}
MVC Client Proj::
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(config =>
{
config.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = "oidc";
config.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:44322/";
options.ClientId = "client_id_mvc";
options.ClientSecret = "client_secret_mvc";
options.SaveTokens = true;
options.ResponseType = "code";
//used for mapping custom clian in Identity to Client.
options.ClaimActions.DeleteClaim("amr");
options.ClaimActions.DeleteClaim("s_hash");
// process -2 This will map Identity claims to current mvc client client.
options.ClaimActions.MapJsonKey("employee_code", "employee_id");
//used for round trips to the Identity, This will hit /userInfo endpoint to get Id token.
options.GetClaimsFromUserInfoEndpoint = true; // process -2
//custom claims
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("custom.employee_profile");
});
services.AddControllersWithViews();
}
Expected result:
Id_token with scope employee_id claim (scope of custom.employee_profile).
Actual:
Missing employee_id claim in Id_token

How to properly setup JwtBearerOptions

I setup Identity Server 4 to issue JWT tokens to authenticate users. In Identity Server 4 I have setup the following:
public class Resources {
public static IEnumerable<ApiResource> GetResources() {
return new[] {
new ApiResource {
Name = "Test.API",
DisplayName = "Test API",
Description = "Allow the user access to the test API",
Scopes = new List<string> { "Core API" },
UserClaims = new List<string> { "General", "Admin" }
}
};
}
public static IEnumerable<ApiScope> GetScopes() {
return new[] {
new ApiScope("Core.API", "Allow access to the test API")
};
}
public static IEnumerable<Client> GetClients() {
return new List<Client>() {
new Client {
ClientName = "Test Client",
ClientId = "b778a2ad-090d-4525-8954-6411de2cd339",
ClientSecrets = new List<Secret> { new Secret("random_text".Sha512()) },
AllowedScopes = new List<string> { "Core.API" },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
},
new Client {
ClientName = "Test Web App",
ClientId = "abb9c89c-a018-4b0f-9a0f-4e701c637665",
ClientSecrets = new List<Secret> { new Secret("other_random_text".Sha512()) },
AllowedGrantTypes = GrantTypes.Hybrid,
RequirePkce = false,
AllowRememberConsent = false,
AllowedScopes = new List<string>
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Address,
StandardScopes.Email,
"Core.API",
"roles"
}
}
};
}
public static IEnumerable<IdentityResource> GetIdentities() {
return new[] {
new IdentityResources.Email(),
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource {
Name = "User Role",
UserClaims = new List<string> { "Admin", "General" }
}
};
}
}
public class Startup {
public Startup(IConfiguration configuration, IWebHostEnvironment appEnv) {
Configuration = configuration;
CurrentEnvironment = appEnv;
}
public IConfiguration Configuration { get; }
private IWebHostEnvironment CurrentEnvironment { get; set; }
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IUserRequester, UserRequester>(_ =>
new UserRequester(Configuration.GetSection("AzureTableStore.UserLogin").Get<TableStoreConfiguration>()));
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
IIdentityServerBuilder builder = services.AddIdentityServer();
if (CurrentEnvironment.IsDevelopment()) {
builder.AddDeveloperSigningCredential();
} else {
X509Certificate2 certData = DownloadCertificate(Configuration.GetSection("APICertificate").Get<Secret>());
builder.AddSigningCredential(certData);
}
builder.AddInMemoryClients(Resources.GetClients());
builder.AddInMemoryIdentityResources(Resources.GetIdentities());
builder.AddInMemoryApiResources(Resources.GetResources());
builder.AddInMemoryApiScopes(Resources.GetScopes());
builder.Services.Configure<TableStoreConfiguration>(Configuration.GetSection("AzureTableStore.UserLogin"));
builder.Services.Configure<RedisConfiguration>(Configuration.GetSection("RedisCache"));
builder.Services.AddTransient<IRedisConnection, RedisConnection>();
builder.Services.AddTransient<IUserRequester, UserRequester>();
builder.Services.AddTransient<IProfileService, ProfileService>();
builder.Services.AddTransient<IResourceOwnerPasswordValidator, PasswordValidator>();
builder.Services.AddTransient<IAuthorizationCodeStore, AuthorizationCodeStore>();
builder.Services.AddTransient<IReferenceTokenStore, ReferenceTokenStore>();
builder.Services.AddTransient<IRefreshTokenStore, RefreshTokenStore>();
builder.Services.AddTransient<IUserConsentStore, UserConsentStore>();
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo {
Version = "v1",
Title = "Authentication",
Description = "API allowing for user requests to be authenticated against their credentials",
Contact = new OpenApiContact {
Name = "Me",
Email = "me#fake.com"
}
});
string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.AddCors(options => options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin().
SetIsOriginAllowedToAllowWildcardSubdomains().
AllowAnyMethod().
AllowAnyHeader().
WithHeaders("X-TEST", "true")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Authentication API v1"));
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}"));
}
private static X509Certificate2 DownloadCertificate(Secret secret) {
KeyVaultSecret secretValue = new Provider(secret.KeyVaultName).GetSecretAsync(secret.SecretName).Result;
var store = new Pkcs12Store();
using (Stream stream = secret.KeyVaultName.Equals("local")
? new FileStream(Environment.GetEnvironmentVariable(secret.SecretName), FileMode.Open)
: new MemoryStream(Convert.FromBase64String(secretValue.Value))) {
store.Load(stream, Array.Empty<char>());
}
string keyAlias = store.Aliases.Cast<string>().SingleOrDefault(a => store.IsKeyEntry(a));
var key = (RsaPrivateCrtKeyParameters)store.GetKey(keyAlias).Key;
var certificate = new X509Certificate2(
DotNetUtilities.ToX509Certificate(store.GetCertificate(keyAlias).Certificate));
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(DotNetUtilities.ToRSAParameters(key));
return RSACertificateExtensions.CopyWithPrivateKey(certificate, rsa);
}
}
and in the Startup.cs of all of my services I have the following:
services.AddAuthentication(configuration.SchemeType).
AddJwtBearer("Bearer", options => {
options.Authority = "https://mytest.com/auth"; // Endpoint of the authentication service
options.TokenValidationParameters = new TokenValidationParameters {
ValidateAudience = false
};
});
// Ensure that the claim type is verified as well
services.AddAuthorization(options => options.AddPolicy("ClientIdPolicy", policy =>
policy.RequireClaim("client_id", "b778a2ad-090d-4525-8954-6411de2cd339", "abb9c89c-a018-4b0f-9a0f-4e701c637665")));
The problem I'm having is that this consistently fails. After trying to debug the issue, I've come to the realization that I don't really understand the purpose of this. Is it validating the fields on the JWT to ensure they're valid? If so, what value should I provide for Authority? Are there any other fields I need to set?
Update:
Upon further investigation, I see that requests return with a WWW-Authenticate response header that contains Bearer error="invalid_token", error_description="The signature key was not found". It appears that I've misconfigured either my Authentication service or my downstream services but I'm not sure which.
Thanks to the article provided by #MichalTrojanowski as well as this post, I was able to determine that there were two problems with how I was authenticating JWTs:
I had my authority set to the wrong value. Or rather, my value for Authority matched the actual endpoint for authorizing tokens but that value did not match what was printed in /.well-known/openid-configuration. Therefore, the authentication failed.
My issuer did not match the iss value in the JWT.
After fixing these two problems, my services have been able to authenticate properly.

Identity Server 4 Getting 401 with valid access token .net Core 3.1

I am using Identity server 4 in my .NET Core 3.1 API Application. I am getting successful token on local server https://localhost:[port]/connect/token and when i use the bearer token to access authorize method , i am always getting 401 error.
I have only 1 controller with [Authorize] added.
Here's my startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddCookiePolicy();
services.AddIdentity<AppUser, IdentityRole>(identityOptions =>
{
identityOptions.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
identityOptions.Lockout.MaxFailedAccessAttempts = 6;
identityOptions.Password.RequiredLength = 8;
})
.AddUserManager<CustomUserManager>()
.AddUserStore<CustomUserStorage>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddSignInManager<CustomSignInManager>()
.AddErrorDescriber<CustomIdentityErrorDescriber>();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
var sqlConnectionString = Configuration.GetConnectionString("SqlServer");
var migrationsAssembly = typeof(AppUser).GetTypeInfo().Assembly.GetName().Name;
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryClients(Config.GetClients())
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(sqlConnectionString, db => MigrationAndRetryBuilder(db, migrationsAssembly));
options.DefaultSchema = AppIdentityDbContext.Schema;
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(sqlConnectionString, db => MigrationAndRetryBuilder(db, migrationsAssembly));
options.DefaultSchema = AppIdentityDbContext.Schema;
});
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = Configuration["Authentication:Authorization"];
options.RequireHttpsMetadata = false;
options.Audience = "ap1";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IdentityServerDatabaseInitialization databaseInitialization)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// app.UseDatabaseErrorPage();
}
else
{
app.UseHsts();
}
if (Configuration.GetValue<bool>("UseSecureHeaders", false))
{
ConfigureAppSecureHeaders(app);
}
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("AllowAll");
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Here's my config.cs:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResources.Address(),
new IdentityResources.Phone()
};
}
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("ap1","My Api")
{
Description="Api with Bearer Token",
Scopes= new []{ new Scope("ap1"), new Scope("offline_access"),new Scope(IdentityServerConstants.LocalApi.ScopeName) },
ApiSecrets= new []{ new Secret("secret".Sha256()) }
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
RequireConsent=false,
ClientId = "ap1",
ClientName="My Api",
AllowOfflineAccess=true,
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// scopes that client has access to
AllowedScopes = { "ap1", "offline_access", IdentityServerConstants.LocalApi.ScopeName },
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
}
};
}
Solution
In the end i decide to separate IdentityServer in its own service and now everything is working with a simple configure
In the end i decide to separate IdentityServer in its own service and now everything is working with a simple configure

PasswordTokenRequest returning invalid_client

I am attempting to get a token from a .net core api controller using a password with identity server 4. I am receiving Error invalid_client.
here is the controller.
[HttpGet]
public async Task<IActionResult> Get()
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:44321");
var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "htmlClient",
ClientSecret = "secretpassword",
UserName = "someguy#gmail.com",
Password = "password",
Scope = "WebApi.ReadAccess"
});
return Ok();
}
here is the config
public class Config
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource(
"WebApi.ReadAccess",
"WebApi API",
new List<string> {
JwtClaimTypes.Id,
JwtClaimTypes.Email,
JwtClaimTypes.Name,
JwtClaimTypes.GivenName,
JwtClaimTypes.FamilyName
}
),
new ApiResource("WebApi.FullAccess", "WebApi API")
};
}
public static IEnumerable<Client> GetClients()
{
return new[]
{
new Client
{
Enabled = true,
ClientName = "HTML Page Client",
ClientId = "htmlClient",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secretpassword")
},
AllowedScopes = { "WebApi.ReadAccess" }
}
};
}
}
in startup.cs in configureServices
services.AddIdentityServer()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddProfileService<ProfileService>()
.AddDeveloperSigningCredential();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme =
JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme =
JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = "https://localhost:44321";
o.Audience = "WebApi.ReadAccess";
o.RequireHttpsMetadata = false;
});
and in configure I have app.UseIdentityServer();
public void Configure(IApplicationBuilder app, IHostingEnvironment env, BooksContext booksContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseIdentityServer();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(options =>
options.SwaggerEndpoint("/swagger/v2/swagger.json", "Book Chapter Service"));
app.UseDefaultFiles();
app.UseStaticFiles();
}
Try changing your code to following. I have given the generic code, you can change it according to your need.
return new List<ApiResource>
{
new ApiResource
{
Name = "api",
DisplayName = "WebApi API",
Scopes =
{
new Scope("WebApi.ReadAccess", "Read write access to web api")
}
},
new ApiResource
{
Name = "api",
DisplayName = "WebApi API",
Scopes =
{
new Scope("WebApi.FullAccess", "Full access to web api")
}
}
}
and
o.Audience = "api";
The reason being,
Your o.Audience name should match ApiResource.Name because it indicates mapping between your authority and audience.
For example in your case Authority https://localhost:44321 has audience lets say called "api".
"api" also is a name of your ApiResource which is giving authority to create access token.
Hope this helps!

Secure asp.net core web api using authorize attributes + identity Server 4

I managed to set up an Identity Server 4 using is4aspid from their templates and it seems to be working fine. Now I'm trying to protect a Web Api using Asp.net Core 2.0. The Authorize seems to be working, but when I try to use [Authorize(Roles ="Admin")] in my controller method it won't work.
I watched this video and tried to do what they did, but I can't find a good chunk of the code they use, like AuthorizationProviderClient or app.UseAuthorizationProvider()
This is the Startup.cs ConfigureServices method of my Identity Server:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer (Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
if (Environment.IsDevelopment())
{
builder.AddDeveloperSigningCredential();
}
else
{
throw new Exception("need to configure key material");
}
services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = "xxxx-xxxx.apps.googleusercontent.com";
options.ClientSecret = "xxxxx";
});
}
And this is the config class:
public static class Config
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
//new ApiResource("api1", "My API #1")
new ApiResource("api1", "My API #1") { UserClaims = { "role" } }
};
}
public static IEnumerable<Client> GetClients()
{
return new[]
{
// https://www.scottbrady91.com/Angular/SPA-Authentiction-using-OpenID-Connect-Angular-CLI-and-oidc-client
new Client {
ClientId = "angular_spa",
ClientName = "Intranet Web App",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = new List<string> { "openid", "profile", "api1" },
RedirectUris = new List<string> { "http://localhost:4200/auth-callback", "http://localhost:4200/silent-refresh.html" },
PostLogoutRedirectUris = new List<string> { "http://localhost:4200/" },
AllowedCorsOrigins = new List<string> { "http://localhost:4200" },
AllowAccessTokensViaBrowser = true
}
};
}
}
And this is the Startup.cs ConfigureServices of my WebApi:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
options.EnableCaching = false;
options.RoleClaimType = System.Security.Claims.ClaimTypes.Role;
});
}
And then the configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
This is how the roles are in the database:
I got this API from their samples. When I send the Bearer token using Postman the Authorize attribute works fine, now I want to get the Roles to work as well. Am I missing a nuget package or something?
Thanks in advance for any help

Categories