Discovery Endpoint¶
The client library for the OpenID Connect discovery endpoint is provided as an extension method for HttpClient
.
The GetDiscoveryDocumentAsync
method returns a DiscoveryResponse
object that has
both strong and weak typed accessors for the various elements of the discovery document.
You should always check the IsError
and Error
properties before accessing the contents of the document.
Example:
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://demo.identityserver.io");
if (disco.IsError) throw new Exception(disco.Error);
Standard elements can be accessed by using properties:
var tokenEndpoint = disco.TokenEndpoint;
var keys = disco.KeySet.Keys;
Custom elements (or elements not covered by the standard properties) can be accessed like this:
// returns string or null
var stringValue = disco.TryGetString("some_string_element");
// return a nullable boolean
var boolValue = disco.TryGetBoolean("some_boolean_element");
// return array (maybe empty)
var arrayValue = disco.TryGetStringArray("some_array_element");
// returns JToken
var rawJson = disco.TryGetValue("some_element");
Discovery Policy¶
By default the discovery response is validated before it is returned to the client, validation includes:
- enforce that HTTPS is used (except for localhost addresses)
- enforce that the issuer matches the authority
- enforce that the protocol endpoints are on the same DNS name as the authority
- enforce the existence of a keyset
Policy violation errors will set the ErrorType
property on the DiscoveryResponse
to PolicyViolation
.
All of the standard validation rules can be modified using the DiscoveryPolicy
class,
e.g. disabling the issuer name check:
var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = "https://demo.identityserver.io",
Policy =
{
ValidateIssuerName = false
}
});
You can also customize validation strategy based on the authority with your own implementation of IAuthorityValidationStrategy
.
By default, comparison uses ordinal string comparison. To switch to Uri comparison:
var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = "https://demo.identityserver.io",
Policy =
{
AuthorityValidationStrategy = new AuthorityUrlValidationStrategy()
}
});
Caching the Discovery Document¶
You should periodically update your local copy of the discovery document, to be able to react to configuration changes on the server. This is especially important for playing nice with automatic key rotation.
The DiscoveryCache
class can help you with that.
The following code will set-up the cache, retrieve the document the first time it is needed, and then cache it for 24 hours:
var cache = new DiscoveryCache("https://demo.identityserver.io");
You can then access the document like this:
var disco = await cache.GetAsync();
if (disco.IsError) throw new Exception(disco.Error);
You can specify the cache duration using the CacheDuration
property
and also specify a custom discovery policy by passing in a DiscoveryPolicy
to the constructor.
Caching and HttpClient Instances¶
By default the discovery cache will create a new instance of HttpClient
every time it needs to access the
discovery endpoint. You can modify this behavior in two ways, either by passing in a pre-created instance into the constructor,
or by providing a function that will return an HttpClient
when needed.
The following code will setup the discovery cache in DI and will use the HttpClientFactory
to create clients:
services.AddSingleton<IDiscoveryCache>(r =>
{
var factory = r.GetRequiredService<IHttpClientFactory>();
return new DiscoveryCache(Constants.Authority, () => factory.CreateClient());
});