DPoP - getting the access token #586
|
Hi, we have an API as part of our IdSvr/ASP.NET Identity site that has a few methods relating to users, eg validate username/email/register. This API is used by several other websites that allow people to apply for memberhip. I've just add proof of possession (DPoP) to one of the sites: builder.Services.AddClientCredentialsTokenManagement()
.AddClient(ApiClients.SsoV2, client =>
{
client.TokenEndpoint = new Uri($"{ssoApiSettings.Uri}/connect/token");
client.ClientId = ClientId.Parse(ssoApiSettings.StudentJoinClientId);
client.ClientSecret = ClientSecret.Parse(ssoApiSettings.StudentJoinClientSecret);
client.Scope = Scope.Parse(ssoApiSettings.StudentJoinScope);
client.DPoPJsonWebKey = DPoPProofKey.Parse(DPoP.CreateJsonWebKey());
});
services.AddClientCredentialsHttpClient(ApiClients.SsoV2, ClientCredentialsClientName.Parse(ApiClients.SsoV2), client =>
{
client.BaseAddress = new Uri(ssoApiSettings.Uri);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());The site calls 2 of the API methods directly which works fine but for one of the methods, register, we retrieve the access token then pass that to our 3rd party membership system which creates the membership and calls the register API on success. private async Task<string> GetSsoAccessToken()
{
string ssoApiAccessToken = nameof(ssoApiAccessToken);
if (!memoryCache.TryGetValue(ssoApiAccessToken, out string? accessToken))
{
var disco = await _httpClient.GetDiscoveryDocumentAsync(_ssoApiSettings.Uri);
var tokenResponse = await _httpClient.RequestClientCredentialsTokenAsync(
new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = _ssoApiSettings.ClientId,
ClientSecret = _ssoApiSettings.ClientSecret,
Scope = _ssoApiSettings.Scope,
DPoPProofToken = DPoPProofKey.Parse(DPoP.CreateJsonWebKey())
}
);
accessToken = tokenResponse.AccessToken!;
var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(30));
memoryCache.Set(ssoApiAccessToken, accessToken, cacheEntryOptions);
}
return accessToken!;
}As you can see above, I have added ClientCredentialsTokenRequest.DPoPProofToken but I get a 400 from RequestClientCredentialsTokenAsync (invalid_dpop_proof - Malformed DPoP token). Can what I am trying to do be done? |
Replies: 1 comment 1 reply
|
Hi @daver77, TL;DR: "Can what I am trying to do be done?" No (but see full answer below). The immediate error ( But there's a deeper issue with the architecture. DPoP tokens are sender-constrained. The access token contains a
This means a DPoP-bound access token is not portable. You can't hand it to your 3rd-party membership system because they don't have your private key and can't produce valid proof headers. The API will reject their calls. The simplest solution: use a separate client without DPoP for the flow where you retrieve a token and pass it to the 3rd party. Your existing DPoP-enabled client continues to handle the direct API calls. builder.Services.AddClientCredentialsTokenManagement()
// DPoP client for direct calls
.AddClient(ApiClients.SsoV2, client =>
{
client.TokenEndpoint = new Uri($"{ssoApiSettings.Uri}/connect/token");
client.ClientId = ClientId.Parse(ssoApiSettings.StudentJoinClientId);
client.ClientSecret = ClientSecret.Parse(ssoApiSettings.StudentJoinClientSecret);
client.Scope = Scope.Parse(ssoApiSettings.StudentJoinScope);
client.DPoPJsonWebKey = DPoPProofKey.Parse(DPoP.CreateJsonWebKey());
})
// Standard bearer token client for tokens passed to 3rd parties
.AddClient(ApiClients.SsoV2ThirdParty, client =>
{
client.TokenEndpoint = new Uri($"{ssoApiSettings.Uri}/connect/token");
client.ClientId = ClientId.Parse(ssoApiSettings.ThirdPartyClientId);
client.ClientSecret = ClientSecret.Parse(ssoApiSettings.ThirdPartyClientSecret);
client.Scope = Scope.Parse(ssoApiSettings.StudentJoinScope);
// No DPoPJsonWebKey — issues a standard bearer token
});This does require a second client registration (https://docs.duendesoftware.com/identityserver/tokens/pop/#enabling-dpop-in-identityserver) in IdentityServer (without |
Hi @daver77,
TL;DR: "Can what I am trying to do be done?" No (but see full answer below).
The immediate error (
invalid_dpop_proof - Malformed DPoP token) is becauseClientCredentialsTokenRequest.DPoPProofTokenexpects a signed DPoP proof JWT, not a raw JWK. You're passingDPoP.CreateJsonWebKey()which is the key material itself. TheAddClientCredentialsTokenManagementlibrary handles proof token creation automatically in your first flow, which is why that works.But there's a deeper issue with the architecture. DPoP tokens are sender-constrained. The access token contains a
cnf(confirmation) claim that cryptographically binds it to the private key that requested it. Every API call must …