With thanks to Scott Brady I was able to answer my question. Here is what I found out...
Client Secrets
As Scott Brady said, Client Secrets are used when your client application calls the token end point. Your client application must have a valid Client Id and Client Secret to call the token end point.
Scope Secrets
But what if your resource server needs to call IdentityServer? This happens when the introspection endpoint is called by the resource server. This occurs when your application uses reference tokens or uses server side validation of JWTs. So suppose your authenticated client makes a call to a protected end point on your resource server. The resource server must then validate that JWT bearer token with IdentityServer. However, the request sent from your resource server to IdentityServer must be a protected call. That is, the resource server must present a JWT bearer token to IdentityServer in order to use the introspection endpoint. The resource server can't use the token that it is trying to validate since that belongs to a client (the audience is not for the resource server, it's for the client application). Additionally, the resource server is not sure if the bearer token is even valid. It wouldn't make sense for the resource server to use the bearer token in question to authenticate the request that validates said bearer token. So now there is a problem... How can the resource server send authenticated requests to IdentityServer?
This is where Scope Secrets come in. The way IdentityServer has solved this problem is to allow you to create a Scope that contains a Scope Secret. This Scope is added in your resource server authentication options. For example:
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5000",
ClientId = "api", //The Scope name
ClientSecret = "api-secret", //This is the non hashed/encrypted Scope Secret
RequiredScopes = new[] { "api" } //Must add the Scope name here
});
By making this Scope required we can ensure that any authenticated client app will have this Scope. Then in IdentityServer you would add the Scope like so:
new Scope
{
Name = "api",
DisplayName = "Scope DisplayName",
Description = "This will grant you access to the API",
//The secret here must be Sha256-ed in order for the /introspection end point to work.
//If the API's IdentityServerBearerTokenAuthenticationOptions field is set as so ValidationMode = ValidationMode.ValidationEndpoint,
//then the API will call the /introspection end point to validate the token on each request (instead of ValidationModel.ValidationLocal.
//The ClientSecret must be the NON Sha256-ed string (for example Api = "api-secret" then scope secret must = "api-secret".Sha256())
//for the token to be validated. There must be a Scope that has the same name as the ClientId field in IdentityServerBearerTokenAuthenticationOptions.
//This is an API authenticates with IdentityServer
ScopeSecrets = new List<Secret>
{
new Secret("api-secret".Sha256())
},
Type = ScopeType.Resource
}
So when the resource server makes the call to the introspection end point it will simply use the Scope Secret as the Client Secret and the Scope name as the Client Id.