Skip to content

Signed Authorize Requests

Instead of providing the parameters for an authorize request as individual query string key/value pairs, you can package them up in signed JWTs. This makes the parameters tamperproof, and you can authenticate the client already on the front-channel.

You can either transmit them by value or by reference to the authorize endpoint - see the spec for more details.

Duende IdentityServer requires the request JWTs to be signed. We support X509 certificates and JSON web keys, e.g.:

var client = new Client
{
ClientId = "foo",
// set this to true to accept signed requests only
RequireRequestObject = true,
ClientSecrets =
{
new Secret
{
// X509 cert base64-encoded
Type = IdentityServerConstants.SecretTypes.X509CertificateBase64,
Value = Convert.ToBase64String(cert.Export(X509ContentType.Cert))
},
new Secret
{
// RSA key as JWK
Type = IdentityServerConstants.SecretTypes.JsonWebKey,
Value = "{'e':'AQAB','kid':'...','kty':'RSA','n':'...'}"
}
}
}

If the request_uri parameter is used, IdentityServer will make an outgoing HTTP call to fetch the JWT from the specified URL.

  1. Enable request URIs

    Enable request URI processing on the Endpoints on the IdentityServerOptions:

    Program.cs
    var idsvrBuilder = builder.Services
    .AddIdentityServer(options =>
    {
    options.Endpoints.EnableJwtRequestUri = true;
    });
  2. Configure the HTTP client

    You can customize the HTTP client used for this outgoing connection, e.g. to set a timeout and limit the size of the response:

    Program.cs
    idsvrBuilder.AddJwtRequestUriHttpClient(client =>
    {
    // defaults to 10 seconds
    client.Timeout = TimeSpan.FromSeconds(5);
    // request objects are small, stop reading after 64 KB
    client.MaxResponseContentBufferSize = 64 * 1024;
    });

    Do not add retries to this HTTP client. The caller controls the URL, so retries only increase the number of outgoing requests.

  3. Block requests to your internal network

    Add an HTTP handler that blocks requests to non-public IP addresses, as described in protecting against SSRF.

IdentityServer typically runs inside your network, where it can reach services that are not reachable from the internet, such as databases, internal APIs, and the cloud metadata endpoint at 169.254.169.254. With request URIs enabled, a caller can make IdentityServer send requests to these services. This is called Server-Side Request Forgery (SSRF).

The caller does not see the response, but the error returned by IdentityServer reveals whether a host and port exist on your network. See the security considerations in RFC 9101 section 10.4 for more details.

To protect against SSRF, use an HTTP handler that refuses to connect to non-public IP addresses, such as loopback, private network, and link-local addresses. The idunno.Security.Ssrf library provides such a handler:

Terminal window
dotnet add package idunno.Security.Ssrf
Program.cs
using idunno.Security;
idsvrBuilder.AddJwtRequestUriHttpClient(client =>
{
client.Timeout = TimeSpan.FromSeconds(5);
client.MaxResponseContentBufferSize = 64 * 1024;
})
.ConfigurePrimaryHttpMessageHandler(() =>
SsrfSocketsHttpHandlerFactory.Create());

The handler validates the IP address when the connection is opened, and connects to the validated address. By default, it only allows https URLs, does not follow redirects, and does not use a proxy. See the idunno.Security.Ssrf documentation for its configuration options.

When you enable request URIs, we also recommend that you:

  • Keep the handler’s defaults that only allow https request URIs, as required by RFC 9101 section 5.2, and that disable redirects and proxies
  • Exclude this HTTP client from retries that apply to all HTTP clients, such as the .NET Aspire service defaults
  • Restrict outgoing network traffic from your IdentityServer hosts, and protect cloud metadata endpoints (for example, by requiring IMDSv2 on AWS)

You can access the validated data from the request object in two ways:

  • Wherever you have access to the ValidatedAuthorizeRequest, the RequestObjectValues dictionary holds the values.
  • In the UI code you can call IIdentityServerInteractionService.GetAuthorizationContextAsync, the resulting AuthorizationRequest object contains the RequestObjectValues dictionary as well.