Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions identity-server/clients/src/ConsoleIntrospectionClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
IDiscoveryCache _cache = new DiscoveryCache(authority);

var response = await RequestTokenAsync();
await IntrospectAsync(response.AccessToken);
await IntrospectAsync(response.AccessToken, ResponseFormat.Json);
await IntrospectAsync(response.AccessToken, ResponseFormat.Jwt);

// Graceful shutdown
Environment.Exit(0);
Expand All @@ -34,14 +35,14 @@ async Task<TokenResponse> RequestTokenAsync()

UserName = "bob",
Password = "bob",
Scope = "resource1.scope1 resource2.scope1"
Scope = "resource1.scope1 resource2.scope1",
});

if (response.IsError) throw new Exception(response.Error);
return response;
}

async Task IntrospectAsync(string accessToken)
async Task IntrospectAsync(string accessToken, ResponseFormat responseFormat)
{
var disco = await _cache.GetAsync();
if (disco.IsError) throw new Exception(disco.Error);
Expand All @@ -53,7 +54,8 @@ async Task IntrospectAsync(string accessToken)

ClientId = "urn:resource1",
ClientSecret = "secret",
Token = accessToken
Token = accessToken,
ResponseFormat = responseFormat
});

if (result.IsError)
Expand All @@ -65,6 +67,11 @@ async Task IntrospectAsync(string accessToken)
if (result.IsActive)
{
result.Claims.ToList().ForEach(c => Console.WriteLine($"{c.Type}: {c.Value}"));

if (responseFormat == ResponseFormat.Jwt)
{
Console.WriteLine($"Raw JWT Response: {result.Raw}");
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


using System.Net;
using Duende.IdentityModel;
using Duende.IdentityServer.Endpoints.Results;
using Duende.IdentityServer.Events;
using Duende.IdentityServer.Extensions;
Expand Down Expand Up @@ -155,7 +156,8 @@ private async Task<IEndpointResult> ProcessIntrospectionRequestAsync(HttpContext

// render result
LogSuccess(validationResult.IsActive, callerName);
return new IntrospectionResult(response);
return new IntrospectionResult(response, callerName,
string.Equals(context.Request.Headers.Accept, $"application/{JwtClaimTypes.JwtTypes.IntrospectionJwtResponse}", StringComparison.OrdinalIgnoreCase));
}

private void LogSuccess(bool tokenActive, string callerName) => _logger.LogInformation("Success token introspection. Token active: {tokenActive}, for caller: {callerName}", tokenActive, callerName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
// See LICENSE in the project root for license information.


using System.Security.Claims;
using Duende.IdentityModel;
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Hosting;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using Microsoft.AspNetCore.Http;

namespace Duende.IdentityServer.Endpoints.Results;
Expand All @@ -22,20 +26,71 @@ public class IntrospectionResult : EndpointResult<IntrospectionResult>
/// </value>
public Dictionary<string, object> Entries { get; }

/// <summary>
/// Gets the name of the caller.
/// </summary>
/// <value>
/// Identifier of the request caller.
/// </value>
public string CallerName { get; }

/// <summary>
/// Gets if JWT response was requested.
/// </summary>
/// <value>
/// True if JWT response was requested.
/// </value>
public bool JwtResponseWasRequested { get; }

/// <summary>
/// Initializes a new instance of the <see cref="IntrospectionResult"/> class.
/// </summary>
/// <param name="entries">The result.</param>
/// <exception cref="System.ArgumentNullException">result</exception>
public IntrospectionResult(Dictionary<string, object> entries) => Entries = entries ?? throw new ArgumentNullException(nameof(entries));
public IntrospectionResult(Dictionary<string, object> entries) : this(entries, null, false)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="IntrospectionResult"/> class.
/// </summary>
/// <param name="entries">The result.</param>
/// <param name="callerName">The identifier of the party making the introspection request.</param>
/// <param name="jwtResponseWasRequested">If a JWT response was requested.</param>
/// <exception cref="System.ArgumentNullException">result</exception>
public IntrospectionResult(Dictionary<string, object> entries, string callerName, bool jwtResponseWasRequested)
{
Entries = entries ?? throw new ArgumentNullException(nameof(entries));
CallerName = callerName;
JwtResponseWasRequested = jwtResponseWasRequested;
}
}

internal class IntrospectionHttpWriter : IHttpResponseWriter<IntrospectionResult>
internal class IntrospectionHttpWriter(IIssuerNameService issuerNameService, ITokenCreationService tokenCreationService)
: IHttpResponseWriter<IntrospectionResult>
{
public Task WriteHttpResponse(IntrospectionResult result, HttpContext context)
public async Task WriteHttpResponse(IntrospectionResult result, HttpContext context)
{
context.Response.SetNoCache();

return context.Response.WriteJsonAsync(result.Entries);
if (result.JwtResponseWasRequested)
{
context.Response.Headers.ContentType = $"application/{JwtClaimTypes.JwtTypes.IntrospectionJwtResponse}";
var token = new Token
{
Type = JwtClaimTypes.JwtTypes.IntrospectionJwtResponse,
Issuer = await issuerNameService.GetCurrentAsync(),
Audiences = [result.CallerName],
CreationTime = DateTime.UtcNow,
Claims = [new Claim("token_introspection", ObjectSerializer.ToString(result.Entries), IdentityServerConstants.ClaimValueTypes.Json)]
};
var jwt = await tokenCreationService.CreateTokenAsync(token);

await context.Response.WriteAsync(jwt);
}
else
{
await context.Response.WriteJsonAsync(result.Entries);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ public static Dictionary<string, object> CreateJwtPayloadDictionary(this Token t
}
}

if (token.Type == JwtClaimTypes.JwtTypes.IntrospectionJwtResponse)
{
payload.Remove(JwtClaimTypes.Expiration);
payload.Remove(JwtClaimTypes.NotBefore);
payload.Remove(JwtClaimTypes.Subject);
}

return payload;
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


using System.Text.Json;
using Duende.IdentityModel;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Models;
Expand Down Expand Up @@ -106,6 +107,10 @@ protected virtual Task<Dictionary<string, object>> CreateHeaderElementsAsync(Tok
additionalHeaderElements.Add("typ", Options.LogoutTokenJwtType);
}
}
else if (token.Type == JwtClaimTypes.JwtTypes.IntrospectionJwtResponse)
{
additionalHeaderElements.Add("typ", "token-introspection+jwt");
}

return Task.FromResult(additionalHeaderElements);
}
Expand Down
Loading
Loading