Skip to content

Commit

Permalink
Added BFF token exchange/ITokenRetriever sample
Browse files Browse the repository at this point in the history
  • Loading branch information
josephdecock committed Jun 23, 2023
1 parent 94d524b commit 77d4755
Show file tree
Hide file tree
Showing 133 changed files with 58,335 additions and 0 deletions.
54 changes: 54 additions & 0 deletions IdentityServer/v6/BFF/TokenExchange/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"version": "0.2.0",
"compounds": [
{
"name": "All",
"configurations": ["IdentityServer", "API", "BFF"],
"presentation": {
"hidden": false,
"group": "compunds",
}
},
],
"configurations": [
{
"name": "IdentityServer",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-identityserver",
"program": "${workspaceFolder}/TokenExchange.IdentityServer/bin/Debug/net6.0/TokenExchange.IdentityServer.dll",
"args": [],
"cwd": "${workspaceFolder}/TokenExchange.IdentityServer",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"console": "externalTerminal",
},
{
"name": "API",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-api",
"program": "${workspaceFolder}/TokenExchange.Api/bin/Debug/net6.0/TokenExchange.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/TokenExchange.Api",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"console": "externalTerminal",
},
{
"name": "BFF",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-bff",
"program": "${workspaceFolder}/TokenExchange.Bff/bin/Debug/net6.0/TokenExchange.Bff.dll",
"args": [],
"cwd": "${workspaceFolder}/TokenExchange.Bff",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"console": "externalTerminal",
},
]
}
54 changes: 54 additions & 0 deletions IdentityServer/v6/BFF/TokenExchange/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"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-identityserver",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/TokenExchange.IdentityServer/TokenExchange.IdentityServer.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-api",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/TokenExchange.Api/TokenExchange.Api.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-bff",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/TokenExchange.Bff/TokenExchange.Bff.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Microsoft.AspNetCore.Mvc;
using System;
using Microsoft.AspNetCore.Authorization;

namespace TokenExchange.Api
{
[AllowAnonymous]
public class EchoController : ControllerBase
{
[HttpGet("{**catch-all}")]
public IActionResult Get()
{
string message;
var sub = User.FindFirst("sub");

if (!User.Identity.IsAuthenticated)
{
message = "Hello, anonymous caller";
}
else if (sub != null)
{
var userName = User.FindFirst("name");
message = $"Hello user, {userName.Value}";
}
else
{
var client = User.FindFirst("client_id");
message = $"Hello client, {client.Value}";
}

var response = new
{
path = Request.Path.Value,
message = message,
time = DateTime.UtcNow.ToString(),
headers = Request.Headers
};

return Ok(response);
}
}
}
58 changes: 58 additions & 0 deletions IdentityServer/v6/BFF/TokenExchange/TokenExchange.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

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

namespace TokenExchange.Api
{
public class Program
{
public static int Main(string[] args)
{
Console.Title = "Simple API";
Activity.DefaultIdFormat = ActivityIdFormat.W3C;

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.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)
{
return Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"Api": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
75 changes: 75 additions & 0 deletions IdentityServer/v6/BFF/TokenExchange/TokenExchange.Api/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Serilog;

namespace TokenExchange.Api
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();

services.AddAuthentication("token")
.AddJwtBearer("token", options =>
{
options.Authority = "https://localhost:5001";
options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = false,
ValidTypes = new[] { "at+jwt" },
NameClaimType = "name",
RoleClaimType = "role"
};
});

services.AddAuthorization(options =>
{
options.AddPolicy("ApiCaller", policy =>
{
policy.RequireClaim("scope", "api");
});
options.AddPolicy("RequireInteractiveUser", policy =>
{
policy.RequireClaim("sub");
});
});
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost,
});

app.UseSerilogRequestLogging();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireAuthorization("ApiCaller");
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IdentityModel" Version="6.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using System.Net.Http;
using System.Threading.Tasks;
using Duende.Bff;
using IdentityModel;
using IdentityModel.Client;
using Microsoft.Extensions.Logging;

namespace TokenExchange.Bff;

public class ImpersonationAccessTokenRetriever : DefaultAccessTokenRetriever
{
public ImpersonationAccessTokenRetriever(ILogger<ImpersonationAccessTokenRetriever> logger) : base(logger)
{
}

public override async Task<AccessTokenResult> GetAccessToken(AccessTokenRetrievalContext context)
{
var result = await base.GetAccessToken(context);

if(result is BearerTokenResult bearerToken)
{
var client = new HttpClient();
var exchangeResponse = await client.RequestTokenExchangeTokenAsync(new TokenExchangeTokenRequest
{
Address = "https://localhost:5001/connect/token",
GrantType = OidcConstants.GrantTypes.TokenExchange,

ClientId = "spa",
ClientSecret = "secret",

SubjectToken = bearerToken.AccessToken,
SubjectTokenType = OidcConstants.TokenTypeIdentifiers.AccessToken
});
if(exchangeResponse.IsError)
{
return new AccessTokenRetrievalError($"Token exchanged failed: {exchangeResponse.ErrorDescription}");
}
if(exchangeResponse.AccessToken is null)
{
return new AccessTokenRetrievalError("Token exchanged failed. Access token is null");
} else
{
return new BearerTokenResult(exchangeResponse.AccessToken);
}
}

return result;
}
}
Loading

0 comments on commit 77d4755

Please sign in to comment.