Can I use just Database in IdentityServer4 instead of writing all clients in Config.cs - asp.net-core

I have been searching for how IdentityServer4 uses DB. I have read: https://docs.identityserver.io/en/release/quickstarts/8_entity_framework.html and looked at the QuickStart4 which uses a DB store. What I can't find is how I can use it in a many clients scenario where we want to add client details to DB only without having to add the client to the config.cs in Identity Server like so:
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
// machine to machine client
new Client
{
ClientId = "client",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ClientCredentials,
// scopes that client has access to
AllowedScopes = { "api1" }
},
// interactive ASP.NET Core MVC client
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris = { "https://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
}
}

see this page Entity Framework Integration and this page Entity Framework Support
Basically, what you need to do is:
Add this NuGet package IdentityServer4.EntityFramework
Apply the migrations to create the necessary tables or use the pre-made SQL scripts here
Add the AddConfigurationStore to your startup class
Alternatively, you implement your own IClientStore implementation.

Related

ApiResource returns "invalid_scope" identityserver

I am implementing Identity Server in a razor page application.
When requesting the speech ApiResource, identityserver returns "invalid_scope". My understanding is that the resource is a group of scopes. So, I was expecting the identityserver to return the scopes defined in the speech resource.
Note: Which I add speech as ApiScope it works fine but then it does not add the speech.synthesize and payment.subscription scopes.
Here's how I have defined the ApiScopes:
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("speech.synthesize", "Speech synthesis",new []{"api.create" }),
new ApiScope("payment.subscription", "Subscription service"),
new ApiScope("payment.manage", "Manage Payment"),
};
And here's how I have defined the ApiResource:
public static IEnumerable<ApiResource> ApiResources =>
new List<ApiResource>
{
new ApiResource("speech", "Speech API")
{
Scopes = { "speech.synthesize", "payment.subscription" }
}
};
And here's the client configuration:
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
AlwaysSendClientClaims = true,
// scopes that client has access to
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"speech"
}
}
};
What is wrong here? Can anybody help me understand the problem.
What is the role of the Api Resource if not grouping the scopes.
You as a client asks for ApiScopes, not ApiResources. One more more ApiResource can point to an ApiScope.
An ApiResource represents an API instance, not a Scope. ApiResources are like clients, but for Apis.
See my answer here for more details about the difference between IdentityResource, ApiResource and ApiScope

Unauthorised client when requesting for token using IdentityServer4 .NET Core 3.1

I'm trying to get IdentityServer4 to work but unfortunately no luck. I'll explain the issue in more detail. I'm using IdentityServer4 and also .NET core Identity. I have a .net core mvc application which has login page. You basically login with username and password. When you login I need to generate jwt token I'm doing this using the following code:
[HttpGet]
public async Task<IActionResult> GetClientToken(string clientId, string clientSecret, string grantType, string scope, string username, string password)
{
var serverClient = HttpClientFactory.CreateClient();
var discoveryDocument = await serverClient.GetDiscoveryDocumentAsync($"{Request.Scheme}://{Request.Host.Value}");
var tokenClient = HttpClientFactory.CreateClient();
var tokenResponse = await tokenClient.RequestPasswordTokenAsync(
new PasswordTokenRequest
{
ClientId = clientId,
ClientSecret = clientSecret,
GrantType = grantType,
Address = discoveryDocument.TokenEndpoint,
UserName = username,
Password = password,
Scope = scope,
});
if (!tokenResponse.IsError)
{
return Ok(new TokenResponseModel()
{
access_token = tokenResponse.AccessToken,
refresh_token = tokenResponse.RefreshToken,
expires_in = tokenResponse.ExpiresIn,
scope = tokenResponse.Scope,
token_type = tokenResponse.TokenType,
});
}
return BadRequest(tokenResponse.Error);
}
Every time I request for a token I get unauthorised client.
My seeding data is as follows:
public static IEnumerable<ApiResource> GetApis() =>
new List<ApiResource>
{
new ApiResource("AppointmentBookingApi"),
new ApiResource("PaymentApi", new string[] { "patient.portal.api.payment" }),
};
public static IEnumerable<IdentityResource> GetIdentityResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResource
{
Name = "patient.portal.api",
UserClaims =
{
"patient.portal",
},
}
};
public static IEnumerable<Client> GetClients() =>
new List<Client>
{
new Client
{
ClientId = "patient.portal.client.refresh",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RequirePkce = true,
RedirectUris = { "https://localhost:44307/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:44307/Home/Index" },
AllowedScopes =
{
"AppointmentBookingApi",
"PaymentApi",
IdentityServerConstants.StandardScopes.OpenId,
"patient.portal.api",
},
AllowOfflineAccess = true,
RequireConsent = false,
},
new Client
{
ClientId = "patient.portal.client.code",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes =
{
"AppointmentBookingApi",
},
},
};
does anyone know where I'm I going wrong here????

Fail to access API when using IdentityServer 4 on a MAC

I have 2 ASP.NET Core 2.1 applications (Auth and API) using Identity Server 4.
On API application Startup I have the following:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(x =>
x.ApiName = "api";
x.Authority = "https://localhost:5005";
x.RequireHttpsMetadata = false;
});
On Auth application Startup I have the following configuration:
services
.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetTestUsers());
Where Config class is the following:
public class Config {
public static List<ApiResource> GetApiResources() {
return new List<ApiResource> {
new ApiResource("api", "API Resource")
};
}
public static List<IdentityResource> GetIdentityResources() {
return new List<IdentityResource> {
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static List<Client> GetClients() {
return new List<Client> {
new Client {
ClientId = "app",
ClientName = "APP Client",
ClientSecrets = { new Secret("pass".Sha256()) },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = {
"api"
}
}
}
public static List<TestUser> GetTestUsers() {
return new List<TestUser> {
new TestUser {
SubjectId = "1",
Username = "john",
Password = "john",
Claims = new List<Claim> { new Claim("name", "John") } },
};
}
}
With Postman I am able to access .well-known/openid-configuration and create an Access Token.
However, when I call the API I get the following error:
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
Exception occurred while processing message.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: '[PII is hidden by default.
Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'. ---> System.IO.IOException: IDX20804: Unable to retrieve document from: '[PII is hidden by default.
Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'. ---> System.Net.Http.HttpRequestException:
The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
I ran dotnet dev-certs https --trust; and the localhost certificate appears on Mac Keychain.
Important
On a Windows computer the code I posted works fine.
I am able to access .well-known/openid-configuration and the API endpoint.
What am I missing?

How to properly use claims with IdentityServer4?

I'm trying to understand how this works, so please bear with me.
Here is my config for identity server:
public static IEnumerable<ApiResource> GetApiResources(IConfiguration configuration)
{
return new []
{
new ApiResource
{
Name = "invoices.api",
ApiSecrets =
{
new Secret("invoices.api.secret".Sha256()),
},
Scopes =
{
new Scope("invoices.api.scope"),
},
UserClaims =
{
"custom_role",
}
}
};
}
public static IEnumerable<Client> GetClients(IConfiguration configuration)
{
return new []
{
new Client
{
ClientId = "invoices.ui",
RequireConsent = false,
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
AccessTokenType = AccessTokenType.Reference,
AllowedCorsOrigins = configuration.GetSection("Redirect").Get<RedirectOptions>().AllowedCorsOrigins.ToList(),
RedirectUris = configuration.GetSection("Redirect").Get<RedirectOptions>().RedirectUris.ToList(),
PostLogoutRedirectUris = configuration.GetSection("Redirect").Get<RedirectOptions>().PostLogoutRedirectUris.ToList(),
ClientSecrets =
{
new Secret("invoices.ui.secret".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"invoices.api.scope",
},
}
};
}
public static IEnumerable<TestUser> GetUsers(IConfiguration configuration)
{
return new []
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "123",
Claims =
{
new Claim("custom_role", "user"),
},
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "123",
Claims =
{
new Claim("custom_role", "admin"),
},
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources(IConfiguration configuration)
{
return new []
{
new IdentityResources.OpenId(),
};
}
And this is how my MVC client is setup:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(opts =>
{
//opts.ExpireTimeSpan = TimeSpan.FromSeconds(60);
})
.AddOpenIdConnect("oidc", opts =>
{
opts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DisableTelemetry = true;
opts.Authority = Configuration.GetValue<string>("IdentityServer");
opts.RequireHttpsMetadata = false;
opts.ClientId = "invoices.ui";
opts.ClientSecret = "invoices.ui.secret";
opts.ResponseType = "code id_token";
opts.SaveTokens = true;
opts.GetClaimsFromUserInfoEndpoint = true;
opts.Scope.Clear();
opts.Scope.Add("openid");
opts.Scope.Add("invoices.api.scope");
});
After a user is authenticated, i'm trying to see it's claims in view like this:
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
But the list doesn't contain any "custom_role" claim.
The identity server logs shows that the user info has been requested by the client from user info endpoint, but my "custom_role" wasn't transfered there, however it shows in logs of identity server, that user has it.
How to access my custom claims in my MVC app?
I need to get them from user endpoint and use for authorization.
If you ask for an Access Token and Identity Token ("code id_token") Identity Server will not include user claims by default.
The solution is to set AlwaysIncludeUserClaimsInIdToken to true. See http://docs.identityserver.io/en/release/reference/client.html
The explanation on why this settings exists is here: https://leastprivilege.com/2016/12/14/optimizing-identity-tokens-for-size/
Seems that adding an identity resource with specified claims solves the problem even with built-in ProfileService implementation:
public static IEnumerable<IdentityResource> GetIdentityResources(IConfiguration configuration)
{
return new []
{
new IdentityResources.OpenId(),
new IdentityResource
{
Name = "roles.scope",
UserClaims =
{
"custom_role",
}
}
};
}
Also added it as a scope for a client:
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"invoices.api.scope",
"roles.scope",
},

WWW-Authenticate header in Identity Server 4 Unauthorized response

I'm protecting a Web API with Identity Server 4.
If an external app tries to access it using client credentials but does not pass in the access token, I get, as expected, the Unauthorized response.
The problem here is that the response does not include the WWW-Authenticate header as I was expecting, as stated in the OAuth spec.
Am I missing some config in Identity Server? Or is it something wrong with the Identity Server implementation?
The relevant code parts follow:
Client registration on Identity Server:
new Client()
{
ClientId = "datalookup.clientcredentials",
ClientName = "Data Lookup Client with Client Credentials",
AlwaysIncludeUserClaimsInIdToken = true,
AlwaysSendClientClaims = true,
AllowOfflineAccess = false,
ClientSecrets =
{
new Secret("XXX".Sha256())
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes =
{
Scopes.DataLookup.Monitoring,
Scopes.DataLookup.VatNumber
},
ClientClaimsPrefix = "client-",
Claims =
{
new Claim("subs", "1000")
}
}
ApiResource registration on Identity Server:
new ApiResource()
{
Name = "datalookup",
DisplayName = "Data Lookup Web API",
ApiSecrets =
{
new Secret("XXX".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.Profile,
"user-subs"
},
Scopes =
{
new Scope()
{
Name = Scopes.DataLookup.Monitoring,
DisplayName = "Access to the monitoring endpoints",
},
new Scope()
{
Name = Scopes.DataLookup.VatNumber,
DisplayName = "Access to the VAT Number lookup endpoints",
Required = true
}
}
}
Authentication configuration in the Web API:
public void ConfigureServices(IServiceCollection services)
{
(...)
services.AddMvc();
services
.AddAuthorization(
(options) =>
{
options.AddPolicy(
Policies.Monitoring,
(policy) =>
{
policy.RequireScope(Policies.Scopes.Monitoring);
});
options.AddPolicy(
Policies.VatNumber,
(policy) =>
{
policy.RequireScope(Policies.Scopes.VatNumber);
policy.RequireClientSubscription();
});
});
services.AddAuthorizationHandlers();
services
.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(
(options) =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "datalookup";
});
(...)
}
Client accessing the Web API:
using (HttpClient client = new HttpClient())
{
// client.SetBearerToken(accessToken);
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, Constants.WebApiEndpoint))
{
using (HttpResponseMessage response = await client.SendAsync(request).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
ConsoleHelper.WriteErrorLine(response);
return;
}
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
ConsoleHelper.WriteInformationLine(content);
}
}
}
Notice that client.SetBearerToken(accessToken) is commented, so that is why I was expecting the response to include the WWW-Authenticate header.
The whole idea behind this is to implement a feature on a client library to deal with the Http Bearer challenge (as, for example, the Azure KeyVault client library does).