Identity Server 4 - unauthorized client - c#

I am struggling with basic setup of the Identity Server 4 with Net Core 3.0 and React (but this is almost irrelevant).
I have generated new app by dotnet new react -au Individual, updated dependencies etc, Created config basically copied from the demo server with the following:
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// JavaScript Client
new Client
{
Enabled = true,
ClientId = "spa",
ClientName = "SPA (Code + PKCE)",
RequireClientSecret = false,
RequireConsent = false,
RedirectUris = { "https://notused" },
PostLogoutRedirectUris = { "https://notused" },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = { "openid", "profile", "email", "api" },
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse
},
};
}
In my startup:
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer(o =>
{
o.UserInteraction.ErrorUrl = "/myErrorsHandler";
o.Events.RaiseErrorEvents = true;
o.Events.RaiseFailureEvents = true;
o.Events.RaiseInformationEvents = true;
o.Events.RaiseSuccessEvents = true;
})
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddInMemoryClients(Config.GetClients()) ;
Then I am trying in Postman:
and always getting:
{"displayMode":null,"uiLocales":null,"error":"unauthorized_client","errorDescription":"Unknown client or client not enabled","requestId":"0HLPL86NBMDRG:00000001","redirectUri":null,"responseMode":null,"clientId":"spa"}
I really don't understand why this is not working.
The same client on demo server with the same in Postman dialog works without any issues.
UPDATE:
I found this docs: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-3.0#application-profiles
but I am still not able to get it working.
It recognizes the client, but despite the config (SPA, IdentityServerSPA) throwing:
{"displayMode":null,"uiLocales":null,"error":"invalid_request","errorDescription":"code challenge required","requestId":"0HLPL8VD22382:00000001","redirectUri":"http://localhost:5000/authentication/login-callback?error=invalid_request&error_description=code%20challenge%20required#_=_","responseMode":"query","clientId":"spa"}
UPDATE 2:
It is "working" with client defined in configuration JSON but only with predefined templates as per doc, but it is impossible (or possibility is not documented) to disable PKCE to make it work e.g. with Postman etc.

You're not defining the client_secret. Based on the code you've provided on the client's configuration you did not setup a client secret, so If no client secret is specified, there's no direct way for your client to prove its authenticity to your Authority (IDserver). This is when PKCE comes in handy, at least you can guarantee that same system is doing both requests.
I see you're asking to disable PKCE, that should not be possible (I'm not sure if it can be done but you definitely shouldn't do that) because you're using code authentication grant for an SPA. (which is the current recommended way of doing things)
As an SPA is a non-confidential client (uncapable of keeping a secret secure) this means that any other application could use your client_id spa to make requests to the token endpoint. To prevent this we combine two things:
Redirect URI: this enforces the response code token to be redirected to a previously known address which should be your client (unless using hosts file to suplant your site)
PKCE: a mechanism that aims to guarantee that both /authorize and /token requests come from the same client, so even if someone manages to intercept the code, he/she should not be able to use it in exchange for a token, because not knowing the original secret used in PKCE.

I struggled with the unauthorized_client error for the Resource Owner Password Validation flow in IdentityServer 4 because the grant type password was missing in [dbo].[ClientGrantTypes] for the associated client_id.
I had to insert a new line into the table to fix this error.
INSERT INTO [dbo].[ClientGrantTypes] (
[GrantType]
,[ClientId])
VALUES ('password', X) --where X is value of [dbo].[Clients].[Id] of used client

Related

IdentityServer4 ClientCredentials basic question (I think)

This is probably a basic question about IdentityServer4
So Im working through the identityserver4 docs
Ive done the
[Protecting an API using Client Credentials] https://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html#
I get that so I set up an APi Resource with an Api Scope (api1) and my "Client" uses Client Credentials and that scope
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
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" }
}
};
ok so then I do the next section "Interactive Applications with ASP.NET Core"
and I get that
So my Client will need to do both so happily there is the next section
"ASP.NET Core and API access" - to bring them both together which says all I have to do is
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" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
which does work
What I dont understand is - it is the IdentityServer4 client "client" that has the
AllowedGrantTypes = GrantTypes.ClientCredentials
so how does "mvc" Client only have to add "api1" to the allowed scope to get the Client Credentials?
I cant see an explanation in the docs
is a bearer token by convention "Client Credentials" (so I dont need the Client "client" anymore) ? or does IdentityServer4 somehow link the Clients based on the fact they both have the api1 scope?
Thanks
ClientCredentials, is for service-to-service communication. Perhaps you have a background job that needs to talk to an API. For for one API to talk to another API.
The authorization code flow is typically for MVC application where you have a user logging in to the site and you get back an access token that you can then send to a given API, like API1 in this case.
In your scenario you don't really need to use both. Only the authorization code flow is enough for the public facing server with api1 as the scope.
You use bearer tokens (authorization header) in both flows when you talk to an API.

ServiceStack Session ID in URL

Good day, I am not that familiar with this topic so excuse me if it's not explained in the best way.
I am using ServiceStack as my backend API and enabled "AllowSessionIdsInHttpParams = true". I am passing the ss-id via my url. I noticed that I can copy that ss-id and use in different clients (browser).
Should the ss-id not be validated against the user agent / client and only be validate if the client is the same?
backend code
//Configure Host
SetConfig(new HostConfig
{
DebugMode = false,
AllowSessionIdsInHttpParams = false,
UseSecureCookies = true,
UseSameSiteCookies = true,
AllowNonHttpOnlyCookies = true
});
// Plugins
Plugins.Add(new CorsFeature(AppConfig.allowedOrigins, "GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD", "Content-Type, Authorization, Accept, X-ss-id", true));
frontend
var instance = axios.create({
baseURL: Vue.prototype.$AppConfig.URL_API,
withCredentials: true,
})
Settings
Both backend and frontend is HTTPS.
setting allowedOrigins is set instead of wildcard (*) which gave different issue if I used.
Error:
401 -
Should the ss-id not be validated against the user agent / client
No, cookies are used to identify a session that given it's a unique id with high entropy is the least predictable identifier sent by HTTP clients. It's not much different to other bearer tokens such as a JWT Tokens or API Keys which authenticates the Bearer as-is, i.e. without additional user agent validation.
Validating against a User-agent would just be security theatre which is common, highly predictable & spoofable, i.e. if they're able to access your Cookie from HTTP Headers they also have access to all other HTTP Headers.
You could validate it against an IP Address but that would break sessions on a network change, e.g. whenever a device roams Wifi points which is why it's typically not done.
To best secure your cookies they should be only sent over SSL as Secure and HTTP Only Cookies (i.e. defaults in ServiceStack):
SetConfig(new HostConfig {
UseSecureCookies = true,
UseHttpOnlyCookies = true,
});
For additional protection against CSRF you can enable SameSite Cookies:
SetConfig(new HostConfig {
UseSameSiteCookies = true,
});
Although as this can break cross-site functionality and 3rd Party OAuth providers like Twitter OAuth it's set to Lax by default.
This is not a direct answer to the question, but relates to the question. This is the preferred and best solution to accomplish what I needed to accomplish in the technology stack I am using.
Servicestack backend Config:
//Configure option also Host UseSameSiteCookies = true
SetConfig(new HostConfig
{
UseSecureCookies = true
});
Plugins.Add(new CorsFeature(AppConfig.allowedOrigins, // configured Origins
"GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD",
"Content-Type, Authorization, Accept",
true)); // allowed Credentials
UseSecureCookies must be true, wildcard (*) origins did not work, allowed credentials must be true.
Axios FrontEnd (config instance for Axios)
var instance = axios.create({
baseURL: Vue.prototype.$AppConfig.URL_API,
withCredentials: true,
})
Using the instance does help to eliminate bowlerplate code, withCredentials can also be set with interceptor, but this code for me is good.
Lastly and for the people like myself who is new to this, don't forget SSL (HTTPS). In IIS you can easily create self sighed certificate for intranet I am sure it's more then enough. You need SSL on both sides, your origins (frontend) and servicestack api (backend.)

When using AddOpenIdConnect, why are default Scopes added?

I am trying to set-up an OpenIdConnect within my Startup.cs class of my .NET Core project but I keep being presented with the error:
Message contains error: 'invalid_scope', error_description: 'error_description is null', error_uri: 'error_uri is null'.
I guess the reason for this error is because during my code shown below, I clear the o.Scope list because it seems to contain 2 default scopes:
openid
profile
If I do NOT clear the default scopes, the client I am accessing is unable to recognise my request against a valid application, and therefore my request fails at their side. In this case, I do re-add the scope: "openid" which allows me to make a successful request to my client, but then on the redirection to my RedirectUri I am getting the error mentioned above.
Is the "invalid_scope" error coming from my client? Or is this occurring within the Middleware using by .NET Core?
Should I ask the client to update the scopes at their side to include "profile" so I don't have to clear the default scopes?
services.AddAuthentication().AddOpenIdConnect(socialProvider.ProviderName, o =>
{
o.ClientId = "xx"
o.ClientSecret = "xx"
o.Authority = "xx"
o.CallbackPath = "xx"
// There appear to be 2 scopes added by default here that cause the
// integration for o to not be recognised as an application when we get to o' side.
o.Scope.Clear();
o.Scope.Add("openid");
o.GetClaimsFromUserInfoEndpoint = true;
o.SaveTokens = true;
o.RequireHttpsMetadata = false;
});
AddOpenIdConnect does auto create the OpenIdConnectOptions object, and the default scope is openid and profile. So you should either tell your identity to support both of these scopes or clear our the default scope and add whatever the client is supported

identity server4 getting "invalid_grant" error for the connect/token endpoint when deployed to production

I use Identityserver4 to implement OAUTH2 and the server supports ResourceOwnerPassword and code flow. I use AWS's EC2 to host the app for production.
The weird thing is even the app runs perfectly fine in my dev machine, after deployed to AWS, I keep getting this invalid_grant and I do not know what goes wrong.
Here is my code:
services.AddIdentityServer()
//.AddDeveloperSigningCredential()
.AddSigningCredential(Certificate.GetCertificate())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
new Client
{
ClientId = "client",
ClientName = "client",
ClientSecrets =
{
new Secret("secret".Sha256())
},
RequireClientSecret = false,
RedirectUris = new List<string>(new string[] { "https://www.getpostman.com/oauth2/callback", "http://localhost:8002", "http://192.168.1.5:8002","app.buyingagent:/oauthredirect"}),
AllowedGrantTypes = GrantTypes.Code,
//RequirePkce = true,
AllowedScopes = { "api" },
AllowOfflineAccess = true
}
public static X509Certificate2 GetCertificate()
{
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.OpenExistingOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "cert", false);
return certs.Count > 0 ? certs[0] : null;
}
}
I understand it is not a good practice to save the information in memory, but I just want to get the proof of concept first. For the x509 certificate which is passed to AddSigningCredential for signing token, I created a self-singing certificate in my local machine using makecert and then export it to the trusted store in AWS via RDP. (makecert does not seems avalible in AWS's command line)
I used this command:
makecert -pe -ss MY -$ individual -n "CN=cert" -len 2048 -r
The app runs find locally but in production I keep getting this "invalid_grant" error. (I use Postman to get token) I can visit the connect/authorize end point though(where I can enter client id and password)
The flow fails at connect/authorize end point.
The error message is like this:
POST http://{url}/connect/token
Request Headers:
undefined:undefined
Request Body:
grant_type:"authorization_code"
code:"7cb58d345975af02332f2b67cb71958ba0a48c391e34edabd0d9dd1500e3f24e"
redirect_uri:"https://www.getpostman.com/oauth2/callback"
client_id:"client"
Response Headers:
undefined:undefined
Response Body:
error:"invalid_grant"
invalid_grant
Error
I know the entered info(client id, redirect url) are all correct(all working fine locally) what could go wrong here once deployed to production? Is the certificate not trusted in production or I cannot use in-memory storage for the client and resource? I do not think it is due to the redirect_url because even if I use the password flow which does not even require a redirect_url it still fails in production.
Note: if remove this line AddSigningCredential(Certificate.GetCertificate())(pass no certificate to identityserver4) I would get this same "invalid_grant. So maybe the certificate imported from my dev machine to AWS is invalid?
After turning on the log,
the problem is keyset does not exist
The App has not permission to read the private key in the certificate. After adding the permission the problem is solved.
The initial invalid_grant is so misleading...
Had this problem when I, in my infinite wisdom, set accessToken expiry time equal to refreshToken expiry, and asked for refresh only when access expired.

IdentityServer4 ValidIssuers

Is there any way to tell IdentityServer4's authentication system to allow multiple issuers for the tokens?
I have an application that is using Identity Server to issue bearer tokens, and as long as the front end and the back end use the same URL to get tokens from authentication works fine.
However, I now have a need to have the same site accessed through multiple CNAMEs, meaning that the client will request tokens from two different URLs.
The error that is sent to the logs is:
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware[7]
Bearer was not authenticated. Failure message: IDX10205: Issuer validation failed. Issuer: 'http://domainb.com'. Did not match: validationParameters.ValidIssuer: 'http://domaina.com' or validationParameters.ValidIssuers: 'null'.
The presence of a ValidIssuers collection seems to indicate that you can set multiple places from which the API will accept tokens, but I cannot find anything like that exposed in options exposed by UseIdentityServerAuthentication.
I am aware of the Authority option, but that only allows me to set a single valid authority.
Is there are any way of setting multiple valid issuers, or setting it to use something other than the hostname as the issuer id?
UPDATE
My identity server configuration on the server side looks like this:
services.AddIdentityServer(options => {
options.IssuerUri = "http://authserver"; })
.AddAspNetIdentity<ApplicationUser>();
this is from the auth server side of things.
On the client API, the UseIdentityServerAuthentication call looks like this:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions()
{
Authority = AppSettingsConfigurationRoot["Authentication:AuthorityEndpoint"],
RequireHttpsMetadata = false,
ApiName = "rqapi",
AutomaticAuthenticate = true,
ClaimsIssuer = "http://localhost:5001"
});
The address in the {{AppSettingsConfigurationROot["Authentication:AuthorityEndpoint"] is usually set at the public DNS name of the server so that the token issuer as seen by AngularJS matches the URL of the IdentityServer from the point of view of the C# API.
As Original Poster wrote in a comment, the (now, 2020, deprecated) IdentityServer4.AccessTokenValidation package doesn't expose the right options. To read more about the recent deprecation check this blogpost, but if you still are using it, here's how I solved this issue.
The AddIdentityServerAuthentication(...) extension method is a wrapper (the code is super readable!) to combine two authentication schemes:
JwtBearer
OAuth2Introspection
It uses its own configuration class, and simply doesn't expose all the JwtBearer options (possibly just an omission, possibly because some options are not valid for both schemes.
If -like me- you only need JwtBearer you might get away with simply using just that, and using the ValidIssuers array. So:
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.Authority = "https://example.org";
options.Audience = "foo-api"; // options.ApiName in the IDS4 variant
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuers = new[]
{
"https://example.org", // first issuer
"https://example.com", // some other issuer
},
NameClaimType = "name", // To mimick IDS4's variant
RoleClaimType = "role", // To mimick IDS4's variant
};
});
As far as I understand, this will use example.org as the Authority and get the openid-configuration and so forth from that domain. But any JWT token offered to this API would be accepted as long as one of the ValidIssuers is the iss (issuer claim) in the token.

Categories