Skip to content

Commit

Permalink
Merge pull request #166 from DuendeSoftware/joe/token-exchange-net8
Browse files Browse the repository at this point in the history
TokenExchange - Update to .NET 8
  • Loading branch information
brockallen authored Jan 23, 2024
2 parents db92682 + 43944ed commit 9f5f4b0
Show file tree
Hide file tree
Showing 19 changed files with 354 additions and 287 deletions.
40 changes: 40 additions & 0 deletions IdentityServer/v7/TokenExchange/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"version": "0.2.0",
"compounds": [
{
"name": "Run All",
"configurations": ["IdentityServerHost", "Client"],
"presentation": {
"group": "10-compunds",
}
}
],
"configurations": [
{
"name": "IdentityServerHost",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-identityserverhost",
"program": "${workspaceFolder}/IdentityServerHost/bin/Debug/net8.0/IdentityServerHost.dll",
"args": [],
"cwd": "${workspaceFolder}/IdentityServerHost",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"console": "externalTerminal",
},
{
"name": "Client",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-client",
"program": "${workspaceFolder}/Client/bin/Debug/net8.0/Client.dll",
"args": [],
"cwd": "${workspaceFolder}/Client",
"console": "externalTerminal",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
}
]
}
41 changes: 41 additions & 0 deletions IdentityServer/v7/TokenExchange/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/TokenExchange.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-identityserverhost",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/IdentityServerHost",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-client",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/Client",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IdentityModel" Version="5.2.0" />
<PackageReference Include="IdentityModel" Version="6.2.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Program

static async Task Main(string[] args)
{
Console.Title = "Console Token Exchange Client";
Console.Title = "Client";
Cache = new DiscoveryCache("https://localhost:5001");

// initial token
Expand Down
File renamed without changes.
50 changes: 50 additions & 0 deletions IdentityServer/v7/TokenExchange/IdentityServerHost/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


using Duende.IdentityServer.Models;
using System.Collections.Generic;
using IdentityModel;

namespace IdentityServerHost;

public static class Config
{
public static readonly IEnumerable<ApiScope> Scopes =
new[]
{
new ApiScope("scope1"),
new ApiScope("scope2"),
};

public static IEnumerable<Client> Clients =>
new []
{
// represent the front end client
new Client
{
ClientId = "front.end",
ClientSecrets = { new Secret("secret".Sha256()) },

AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = { "scope1" },

// simulate interactive user
ClientClaimsPrefix = "",
Claims =
{
new ClientClaim("sub", "123")
}
},

// represents the client that is delegating the access token
new Client
{
ClientId = "api1",
ClientSecrets = { new Secret("secret".Sha256()) },

AllowedGrantTypes = { OidcConstants.GrantTypes.TokenExchange },
AllowedScopes = { "scope2" }
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Duende.IdentityServer" Version="7.0.0-rc.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Linq;
using System.Threading.Tasks;
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using IdentityModel;

namespace IdentityServerHost;

public class ProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// add actor claim if needed
if (context.Subject.GetAuthenticationMethod() == OidcConstants.GrantTypes.TokenExchange)
{
var act = context.Subject.FindFirst(JwtClaimTypes.Actor);
if (act != null)
{
context.IssuedClaims.Add(act);
}
}

return Task.CompletedTask;
}

public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.CompletedTask;
}
}
48 changes: 48 additions & 0 deletions IdentityServer/v7/TokenExchange/IdentityServerHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
using System;

namespace IdentityServerHost;

public class Program
{
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code)
.CreateLogger();

try
{
Log.Information("Starting host...");
CreateHostBuilder(args).Build().Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly.");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
File renamed without changes.
33 changes: 33 additions & 0 deletions IdentityServer/v7/TokenExchange/IdentityServerHost/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace IdentityServerHost;

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
Console.Title = "IdentityServer";

var builder = services.AddIdentityServer()
.AddInMemoryApiScopes(Config.Scopes)
.AddInMemoryClients(Config.Clients);

// registers extension grant validator for the token exchange grant type
builder.AddExtensionGrantValidator<TokenExchangeGrantValidator>();

// register a profile service to emit the act claim
builder.AddProfileService<ProfileService>();
}

public void Configure(IApplicationBuilder app)
{
app.UseDeveloperExceptionPage();

app.UseIdentityServer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Duende.IdentityServer;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Validation;
using IdentityModel;

namespace IdentityServerHost;

public class TokenExchangeGrantValidator : IExtensionGrantValidator
{
private readonly ITokenValidator _validator;

public TokenExchangeGrantValidator(ITokenValidator validator)
{
_validator = validator;
}

public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
// defaults
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest);
var customResponse = new Dictionary<string, object>
{
{OidcConstants.TokenResponse.IssuedTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken}
};

var subjectToken = context.Request.Raw.Get(OidcConstants.TokenRequest.SubjectToken);
var subjectTokenType = context.Request.Raw.Get(OidcConstants.TokenRequest.SubjectTokenType);

// mandatory parameters
if (string.IsNullOrWhiteSpace(subjectToken))
{
return;
}

if (!string.Equals(subjectTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken))
{
return;
}

var validationResult = await _validator.ValidateAccessTokenAsync(subjectToken);
if (validationResult.IsError)
{
return;
}

var sub = validationResult.Claims.First(c => c.Type == JwtClaimTypes.Subject).Value;
var clientId = validationResult.Claims.First(c => c.Type == JwtClaimTypes.ClientId).Value;

var style = context.Request.Raw.Get("exchange_style");

if (style == "impersonation")
{
// set token client_id to original id
context.Request.ClientId = clientId;

context.Result = new GrantValidationResult(
subject: sub,
authenticationMethod: GrantType,
customResponse: customResponse);
}
else if (style == "delegation")
{
// set token client_id to original id
context.Request.ClientId = clientId;

var actor = new
{
client_id = context.Request.Client.ClientId
};

var actClaim = new Claim(JwtClaimTypes.Actor, JsonSerializer.Serialize(actor), IdentityServerConstants.ClaimValueTypes.Json);

context.Result = new GrantValidationResult(
subject: sub,
authenticationMethod: GrantType,
claims: new[] { actClaim },
customResponse: customResponse);
}
else if (style == "custom")
{
context.Result = new GrantValidationResult(
subject: sub,
authenticationMethod: GrantType,
customResponse: customResponse);
}
}

public string GrantType => OidcConstants.GrantTypes.TokenExchange;
}
4 changes: 2 additions & 2 deletions IdentityServer/v7/TokenExchange/TokenExchange.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "src\IdentityServer\IdentityServer.csproj", "{391FDE19-D829-4868-9221-B64358475C48}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServerHost", "IdentityServerHost\IdentityServerHost.csproj", "{391FDE19-D829-4868-9221-B64358475C48}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "src\Client\Client.csproj", "{93B5DD40-6D86-4E99-AA89-CBB41F70E40F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{93B5DD40-6D86-4E99-AA89-CBB41F70E40F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Loading

0 comments on commit 9f5f4b0

Please sign in to comment.