Skip to content

Instantly share code, notes, and snippets.

@bangonkali
Created March 1, 2023 07:39
Show Gist options
  • Save bangonkali/f3bf3641653bc41e24dd7560bd2e6c74 to your computer and use it in GitHub Desktop.
Save bangonkali/f3bf3641653bc41e24dd7560bd2e6c74 to your computer and use it in GitHub Desktop.
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Newtonsoft.Json;
namespace Netzon.Dash.Middlewares;
public static class TokenExchangeMiddlewareExtension
{
public static IApplicationBuilder UseTokenExchange(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TokenExchangeMiddleware>();
}
}
public class TokenExchangeMiddleware
{
private readonly RequestDelegate _next;
private readonly IHttpClientFactory _clientFactory;
private readonly ILogger<TokenExchangeMiddleware> _logger;
private readonly IConfiguration _configuration;
public TokenExchangeMiddleware(
RequestDelegate next,
IHttpClientFactory clientFactory,
ILogger<TokenExchangeMiddleware> logger,
IConfiguration configuration
)
{
_next = next;
_clientFactory = clientFactory;
_logger = logger;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
// if not authenticated then just execute to next middleware and return
if (context.User.Identity?.IsAuthenticated != true)
{
await _next(context);
return;
}
// get username, for keycloak you need to regex this to get the clean username
var currentUserName = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// logs an error so it's easier to find - thanks debug.
if (currentUserName != null) _logger.LogInformation($"Current user: {currentUserName}");
//Debug this line of code if you want to validate the content jwt.io
var accessToken = await context.GetTokenAsync("access_token");
var idToken = await context.GetTokenAsync("id_token");
var refreshToken = await context.GetTokenAsync("refresh_token");
// check if accessToken is expired
if (IsTokenExpired(accessToken))
{
var newAccessToken = await GetRefreshTokenAsync(refreshToken);
var serviceAccessToken = await GetTokenExchangeAsync(newAccessToken);
_logger.LogInformation("Access token is expired, refreshing token");
}
// Call the next delegate/middleware in the pipeline.
await _next(context);
}
// function that checks if string jwt is expired
private static bool IsTokenExpired(string token)
{
var handler = new JwtSecurityTokenHandler(); // TODO: optimize
var jsonToken = handler.ReadToken(token);
var tokenS = jsonToken as JwtSecurityToken;
var exp = tokenS.Claims.First(claim => claim.Type == "exp").Value;
var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
return unixTimestamp > long.Parse(exp);
}
private async Task<string> GetRefreshTokenAsync(string refreshToken)
{
/*
* Get refresh token
* Uses the settings injected from startup to read the configuration
*/
try
{
string url = _configuration["Keycloak:TokenExchange"];
//Important the grant type fro refresh token, must be set to this!
string grant_type = "refresh_token";
string client_id = _configuration["Keycloak:ClientId"];
string client_secret = _configuration["Keycloak:ClientSecret"];
string token = refreshToken;
var form = new Dictionary<string, string>
{
{"grant_type", grant_type},
{"client_id", client_id},
{"client_secret", client_secret},
{"refresh_token", token }
};
var client = _clientFactory.CreateClient(); // TODO: optimize
HttpResponseMessage tokenResponse = await client.PostAsync(url, new FormUrlEncodedContent(form));
var jsonContent = await tokenResponse.Content.ReadAsStringAsync();
Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);
return tok.AccessToken;
}
catch (Exception ex)
{
return ex.Message;
}
}
private async Task<string> GetTokenExchangeAsync(string accessToken)
{
/*
* Get exchange token
* ses the settings injected from startup to read the configuration
*/
try
{
string url = _configuration["Keycloak:TokenExchange"];
//Important, the grant types for token exchange, must be set to this!
string grant_type = "urn:ietf:params:oauth:grant-type:token-exchange";
string client_id = _configuration["Keycloak:ClientId"];
string client_secret = _configuration["Keycloak:ClientSecret"];
string audience = _configuration["Keycloak:Audience"];
string token = accessToken;
var form = new Dictionary<string, string>
{
{"grant_type", grant_type},
{"client_id", client_id},
{"client_secret", client_secret},
{"audience", audience},
{"subject_token", token }
};
var client = _clientFactory.CreateClient(); // TODO: optimize
HttpResponseMessage tokenResponse = await client.PostAsync(url, new FormUrlEncodedContent(form));
var jsonContent = await tokenResponse.Content.ReadAsStringAsync();
Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);
return tok.AccessToken;
}
catch (Exception ex)
{
return ex.Message;
}
}
internal class Token
{
[JsonProperty("access_token")] public string AccessToken { get; set; }
[JsonProperty("token_type")] public string TokenType { get; set; }
[JsonProperty("expires_in")] public int ExpiresIn { get; set; }
[JsonProperty("refresh_token")] public string RefreshToken { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment