Skip to content

Commit

Permalink
Merge pull request #168 from DuendeSoftware/joe/remote-api-token-retr…
Browse files Browse the repository at this point in the history
…eival

Joe/remote api token retreival
  • Loading branch information
brockallen committed Jun 1, 2023
2 parents 8e7d494 + 158706c commit 83e9045
Show file tree
Hide file tree
Showing 30 changed files with 1,091 additions and 634 deletions.
4 changes: 2 additions & 2 deletions samples/Api/EchoController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public class EchoController : ControllerBase
public IActionResult Get()
{
string message;
var sub = User.FindFirst(("sub"));
var sub = User.FindFirst("sub");

if (!User.Identity.IsAuthenticated)
{
message = "Hello, anonymous caller";
}
else if (sub != null)
{
var userName = User.FindFirst(("name"));
var userName = User.FindFirst("name");
message = $"Hello user, {userName.Value}";
}
else
Expand Down
8 changes: 7 additions & 1 deletion samples/IdentityServer/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


using Duende.IdentityServer.Models;
using IdentityModel;

namespace IdentityServerHost
{
Expand All @@ -29,7 +30,12 @@ public static class Config
ClientId = "spa",
ClientSecrets = { new Secret("secret".Sha256()) },

AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowedGrantTypes =
{
GrantType.AuthorizationCode,
GrantType.ClientCredentials,
OidcConstants.GrantTypes.TokenExchange
},

RedirectUris = { "https://localhost:5002/signin-oidc" },

Expand Down
1 change: 1 addition & 0 deletions samples/IdentityServer/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde
isBuilder.AddInMemoryIdentityResources(Config.IdentityResources);
isBuilder.AddInMemoryApiScopes(Config.ApiScopes);
isBuilder.AddInMemoryClients(Config.Clients);
isBuilder.AddExtensionGrantValidator<TokenExchangeGrantValidator>();

return builder.Build();
}
Expand Down
70 changes: 70 additions & 0 deletions samples/IdentityServer/TokenExchangeGrantValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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;
}

// register for urn:ietf:params:oauth:grant-type:token-exchange
public string GrantType => OidcConstants.GrantTypes.TokenExchange;

public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
// default response is error
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest);

// the spec allows for various token types, most commonly you return an access token
var customResponse = new Dictionary<string, object>
{
{ OidcConstants.TokenResponse.IssuedTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken }
};

// read the incoming token
var subjectToken = context.Request.Raw.Get(OidcConstants.TokenRequest.SubjectToken);

// and the token type
var subjectTokenType = context.Request.Raw.Get(OidcConstants.TokenRequest.SubjectTokenType);

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

// for our impersonation/delegation scenario we require an access token
if (!string.Equals(subjectTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken))
{
return;
}

// validate the incoming access token with the built-in token validator
var validationResult = await _validator.ValidateAccessTokenAsync(subjectToken);
if (validationResult.IsError)
{
return;
}

// these are two values you typically care about
var sub = validationResult.Claims.First(c => c.Type == JwtClaimTypes.Subject).Value;

var alice = TestUsers.Users.Single(u => u.Username == "alice").SubjectId;
var bob = TestUsers.Users.Single(u => u.Username == "bob").SubjectId;

var impersonateSub = sub == alice ? bob : alice;
var impersonateClaims = TestUsers.Users.Single(u => u.SubjectId == impersonateSub).Claims;

// create response
context.Result = new GrantValidationResult(
subject: impersonateSub,
authenticationMethod: "swap-alice-and-bob",
claims: impersonateClaims);
}
}
53 changes: 53 additions & 0 deletions samples/JS6/ImpersonationAccessTokenRetriever.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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 Host6;

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.Token != null)
{
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 = result.Token,
SubjectTokenType = OidcConstants.TokenTypeIdentifiers.AccessToken
});
if(exchangeResponse.IsError)
{
return new AccessTokenResult
{
IsError = true
};
}
return new AccessTokenResult
{
IsError = false,
Token = exchangeResponse.AccessToken
};
}

return result;
}
}
22 changes: 10 additions & 12 deletions samples/JS6/LocalApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@

using Microsoft.AspNetCore.Mvc;

namespace Host5
namespace Host6;
[Route("local")]
public class LocalApiController : ControllerBase
{
[Route("local")]
public class LocalApiController : ControllerBase
public IActionResult Get()
{
public IActionResult Get()
var data = new
{
var data = new
{
Message = "Hello from local API",
User = User!.FindFirst("name")?.Value ?? User!.FindFirst("sub")!.Value
};
Message = "Hello from local API",
User = User!.FindFirst("name")?.Value ?? User!.FindFirst("sub")!.Value
};

return Ok(data);
}
return Ok(data);
}
}
}
75 changes: 37 additions & 38 deletions samples/JS6/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,47 @@
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;

namespace Host5
namespace Host6;

public class Program
{
public class Program
public static int Main(string[] args)
{
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
.MinimumLevel.Override("IdentityModel", LogEventLevel.Debug)
.MinimumLevel.Override("Duende.Bff", LogEventLevel.Debug)
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code)
.CreateLogger();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
.MinimumLevel.Override("IdentityModel", LogEventLevel.Debug)
.MinimumLevel.Override("Duende.Bff", LogEventLevel.Debug)
.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();
}
try
{
Log.Information("Starting host...");
CreateHostBuilder(args).Build().Run();
return 0;
}

public static IHostBuilder CreateHostBuilder(string[] args)
catch (Exception ex)
{
return Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
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>();
});
}
}
}
Loading

0 comments on commit 83e9045

Please sign in to comment.