IdentityServer4 Introspection Endpoint API uses an invalid hash algorithm

Attempting to validate a token using an introspection endpoint on IdentityServer4. I keep getting 401: Unauthorized. My log is as follows:

dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0] Found MyAPI API resource in database info: IdentityServer4.Validation.HashedSharedSecretValidator[0] Secret: MyAPI API uses invalid hashing algorithm. dbug: IdentityServer4.Validation.SecretValidator[0] Secret validators could not validate secret fail: IdentityServer4.Validation.ApiSecretValidator[0] API validation failed. fail: IdentityServer4.Endpoints.IntrospectionEndpoint[0] API unauthorized to call introspection endpoint. aborting. 

My API is configured like this:

 new ApiResource { Name = "MyAPI", DisplayName = "My API", ApiSecrets = { new Secret("TopSecret".Sha256()) }, } 

I pass the headers for the content type as application / x-www-form-urlencoded and authorization as Basic xxxxxxxxxxxxxxxxx, where x is my base64 encoded string (myapi: TopSecret). My token is in the message body

What am I missing? Why am I getting the "MyAPI API uses the wrong hash algorithm"? If this is not valid, what is a valid hashing algorithm?

Additional Information: My resources are contained in SQL-DB, accessed through the Entity Framework. In particular, the setup is the same as in the quick start documentation found here . To get to what I find, I had to manually add my API to the ApiSecrets table and provide it with a type (SharedSecret) and a value that is a Sha256 password.

In Startup.cs, my COnfigureServices features include

 services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(Configurations.Scopes.GetApiResources()) .AddInMemoryClients(Configurations.Clients.GetClients()) .AddConfigurationStore(builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly))) .AddOperationalStore(builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly))); // include the password validation routine services.AddTransient<IResourceOwnerPasswordValidator, Configurations.ResourceOwnerPasswordValidator>(); services.AddTransient<IProfileService, Configurations.ProfileService>(); services.AddMvc(); 

In the Configure section:

 app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = "http://localhost:5000", RequireHttpsMetadata = false, ApiSecret = "TopSecret", AutomaticAuthenticate = true, AutomaticChallenge = false, ApiName = "MyAPI" }); InitializeDatabase(app); app.UseIdentityServer(); app.UseMvc(); 

Please note that I added ApiSecret, AutomaticAuthenticate and AutomaticChallenge to this section only after I had a problem to get it working.

In my Scopes.cs, I describe the following API:

 public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource { Name = "MyAPI", DisplayName = "My API", ApiSecrets = { new Secret("TopSecret".Sha256()), }, } }; } 

For clients:

 public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientName = "My Client", AlwaysSendClientClaims=true, ClientId = "MyClient", ClientSecrets = { new Secret("TopSecret".Sha256()) }, RequireClientSecret=false, AllowAccessTokensViaBrowser =true, AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, AllowedScopes = { "MyAPI" }, RequireConsent = false, AllowOfflineAccess = true, }, 

This is more or less all that is in the code part. The database that hosts the configuration seems to override any code changes I make, so I'm not sure how useful this is to everyone. In the database, I created an entry in the ApiSecrets table with ApiResourceId of 1, added a description and expiration date, set the type to “SharedSecret” and added “Secret” using various formats, including plain text, sha256 and base64.

Here is the full magazine during the conversation. Perhaps this will help. I see that there are some things that Bearer was not found or something like that, but I'm not sure why this would be, and if it would affect the result of the procedure.

  info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 29.4277ms 401 info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 POST http://localhost:5000/connect/introspect application/x-www-form-urlencoded 762 info: IdentityServer4.AccessTokenValidation.Infrastructure.NopAuthenticationMiddleware[7] Bearer was not authenticated. Failure message: No token found. dbug: IdentityServer4.CorsPolicyProvider[0] CORS request made for path: /connect/introspect from origin: chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo but rejected because invalid CORS path info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware[7] idsrv was not authenticated. Failure message: Unprotect ticket failed dbug: IdentityServer4.Hosting.EndpointRouter[0] Request path /connect/introspect matched to endpoint type Introspection dbug: IdentityServer4.Hosting.EndpointRouter[0] Mapping found for endpoint: Introspection, creating handler: IdentityServer4.Endpoints.IntrospectionEndpoint info: IdentityServer4.Hosting.IdentityServerMiddleware[0] Invoking IdentityServer endpoint: IdentityServer4.Endpoints.IntrospectionEndpoint for /connect/introspect dbug: IdentityServer4.Endpoints.IntrospectionEndpoint[0] Starting introspection request. dbug: IdentityServer4.Validation.BasicAuthenticationSecretParser[0] Start parsing Basic Authentication secret dbug: IdentityServer4.Validation.SecretParser[0] Parser found secret: BasicAuthenticationSecretParser dbug: IdentityServer4.Validation.SecretParser[0] Secret id found: MyAPI info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [apiResource].[Id], [apiResource].[Description], [apiResource].[DisplayName], [apiResource].[Enabled], [apiResource].[Name] FROM [ApiResources] AS [apiResource] WHERE [apiResource].[Name] = @__name_0 ORDER BY [apiResource].[Id] info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] SELECT [a3].[Id], [a3].[ApiResourceId], [a3].[Type] FROM [ApiClaims] AS [a3] INNER JOIN ( SELECT DISTINCT TOP(1) [apiResource].[Id] FROM [ApiResources] AS [apiResource] WHERE [apiResource].[Name] = @__name_0 ORDER BY [apiResource].[Id] ) AS [apiResource2] ON [a3].[ApiResourceId] = [apiResource2].[Id] ORDER BY [apiResource2].[Id] info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] SELECT [a2].[Id], [a2].[ApiResourceId], [a2].[Description], [a2].[Expiration], [a2].[Type], [a2].[Value] FROM [ApiSecrets] AS [a2] INNER JOIN ( SELECT DISTINCT TOP(1) [apiResource].[Id] FROM [ApiResources] AS [apiResource] WHERE [apiResource].[Name] = @__name_0 ORDER BY [apiResource].[Id] ) AS [apiResource1] ON [a2].[ApiResourceId] = [apiResource1].[Id] ORDER BY [apiResource1].[Id] info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] SELECT [a].[Id], [a].[ApiResourceId], [a].[Description], [a].[DisplayName], [a].[Emphasize], [a].[Name], [a].[Required], [a].[ShowInDiscoveryDocument] FROM [ApiScopes] AS [a] INNER JOIN ( SELECT DISTINCT TOP(1) [apiResource].[Id] FROM [ApiResources] AS [apiResource] WHERE [apiResource].[Name] = @__name_0 ORDER BY [apiResource].[Id] ) AS [apiResource0] ON [a].[ApiResourceId] = [apiResource0].[Id] ORDER BY [apiResource0].[Id], [a].[Id] info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] SELECT [a0].[Id], [a0].[ApiScopeId], [a0].[Type] FROM [ApiScopeClaims] AS [a0] INNER JOIN ( SELECT DISTINCT [apiResource0].[Id], [a].[Id] AS [Id0] FROM [ApiScopes] AS [a] INNER JOIN ( SELECT DISTINCT TOP(1) [apiResource].[Id] FROM [ApiResources] AS [apiResource] WHERE [apiResource].[Name] = @__name_0 ORDER BY [apiResource].[Id] ) AS [apiResource0] ON [a].[ApiResourceId] = [apiResource0].[Id] ) AS [a1] ON [a0].[ApiScopeId] = [a1].[Id0] ORDER BY [a1].[Id], [a1].[Id0] dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0] Found MyAPI API resource in database info: IdentityServer4.Validation.HashedSharedSecretValidator[0] Secret: MyAPI Secret uses invalid hashing algorithm. info: IdentityServer4.Validation.HashedSharedSecretValidator[0] Secret: MyAPI Secret uses invalid hashing algorithm. dbug: IdentityServer4.Validation.SecretValidator[0] Secret validators could not validate secret fail: IdentityServer4.Validation.ApiSecretValidator[0] API validation failed. fail: IdentityServer4.Endpoints.IntrospectionEndpoint[0] API unauthorized to call introspection endpoint. aborting. info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 30.673ms 401 
+5
source share
2 answers

Not seeing every little piece of code and database configuration, it's a little complicated. Also, I do not see the code in which you are really invoking the Introspection endpoint. Are you doing this in C # or in Javascript or with Postman?

Anyway, here is my review ...

Startup.cs

The ConfigureServices method looks good. Adding a ResourceOwner password verification service is not required for this issue; ApiSecret is required to access the Introspection endpoint, not the ResourceOwner password. I assume you have this for some unrelated reason, if not, then pull it out.

According to the Configure method, you have app.UseIdentityServerAuthentication , which means that you are using a web application not only to act as an authentication server (using IdentityServer4), but it is also a Web Api application that forwards the authentication server (itself in this case) to check incoming tokens.

 app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = "https://localhost:44388", RequireHttpsMetadata = false, ApiName = "MyAPI" //ApiSecret = "TopSecret" not necessary to know the api secret for normal validation //AutomaticAuthenticate = true, not necessary //AutomaticChallenge = false not necessary }); 

You might also want app.UseMvcWithDefaultRoute() .

Api InMemory Configurations

Using the constructor new ApiResource("name", "display") will correctly configure the database; but using the object initializer syntax, as you did above, will not. This is the issue reported by GitHub: https://github.com/IdentityServer/IdentityServer4/issues/836

 public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { // this will incorrectly leave out the ApiScope record in the database, but will create the ApiResoure and ApiSecret records new ApiResource { Name = "MyAPI", DisplayName = "My API", ApiSecrets = { new Secret("TopSecret".Sha256()), } }, // this will correctly create the ApiResource, ApiScope, and ApiSecret records in the database. new ApiResource("MyAPI2", "My API2") { ApiSecrets = { new Secret("TopSecret2".Sha256()) } } }; } 

Fyi, since no areas are specified in the new ApiResources tag, IdentityServer automatically generates one ApiScope for each ApiResource. In this case, ApiScore gets the same name as ApiResource. ApiResource must have at least one ApiScope; but can have a lot. It is ApiScopes that are then associated with the client in the ClientScopes table.

InMemory Client Configuration

See comments ...

 public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientName = "My Client", AlwaysSendClientClaims = true, ClientId = "MyClient", // changed the secret to make clear this is unrelated to the Api secret ClientSecrets = { new Secret("TopSecretClientSecret".Sha256()) }, // RequireClientSecret might as well be true if you are giving this client a secret RequireClientSecret = true, AllowAccessTokensViaBrowser = true, AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, // Added MyAPI2 from my example above AllowedScopes = { "MyAPI", "MyAPI2" }, RequireConsent = false, AllowOfflineAccess = true } }; } 

Introspection Endpoint Challenge

The following code is from a WebApi controller. (Remember that the IdentityServer and ApiResource credentials are located in the same web application in this discussion). A request for this method will be made by the Client.

Inside this method, you can see that it calls its own endpoint introspection to verify / decrypt the access_token. This is not necessary in this example because we configured the web application on app.UseIdentityServerAuthentication , which already does this. The introspection endpoint will be used for Reference tokens or for cases where the web application itself is unable to verify the access_token.

 [Route("api/[controller]/[action]")] [Produces("application/json")] public class DataController : Controller { [HttpGet] [Authorize] public async Task<IEnumerable<String>> Secure() { var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token"); var introspectionClient = new IntrospectionClient("https://localhost:44388/connect/introspect", "MyAPI", "TopSecret"); var response = await introspectionClient.SendAsync(new IntrospectionRequest { Token = accessToken }); var isActive = response.IsActive; var claims = response.Claims; return new[] { "secure1", "secure2", $"isActive: {isActive}", JsonConvert.SerializeObject(claims) }; } } 

Here, using IntrospectionClient for ApiScope "MyAPI" will give 401, since ApiScope is not in the database due to the previously mentioned object initializer problem.

Last thing

Another possible problem is that manually adding the hashed ApiSecret in the database editor can lead to strange copy / paste problems, making the text incapable of decrypting properly.

View full solution:

https://github.com/travisjs/AspNetCore-IdentityServer-Instrospection

I hope this helps to understand the essence of the problem or at least stimulate a new thought.

+5
source

(MyAPI: TopSecret). My token is in the message body

The introspection endpoint needs basic authentication that uses scope:apisecret , not name:apisecret .

0
source

Source: https://habr.com/ru/post/1264804/


All Articles