Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

161 lignes
5.0KB

  1. using System.IdentityModel.Tokens.Jwt;
  2. using System.Security.Claims;
  3. using Campaign_Tracker.Server.Audit;
  4. using Campaign_Tracker.Server.Authentication;
  5. using Microsoft.AspNetCore.Authorization;
  6. using Microsoft.AspNetCore.Mvc;
  7. namespace Campaign_Tracker.Server.Controllers;
  8. [ApiController]
  9. [AllowAnonymous]
  10. [Route("api/auth/token")]
  11. public sealed class AuthTokenController : ControllerBase
  12. {
  13. private readonly IHostEnvironment _environment;
  14. private readonly IAuditService _auditService;
  15. private readonly IKeycloakTokenClient _tokenClient;
  16. public AuthTokenController(
  17. IKeycloakTokenClient tokenClient,
  18. IAuditService auditService,
  19. IHostEnvironment environment)
  20. {
  21. _auditService = auditService;
  22. _environment = environment;
  23. _tokenClient = tokenClient;
  24. }
  25. [HttpPost("exchange")]
  26. public async Task<ActionResult<AuthTokenSetResponse>> Exchange(
  27. [FromBody] AuthorizationCodeExchangeRequest request,
  28. CancellationToken cancellationToken)
  29. {
  30. if (string.IsNullOrWhiteSpace(request.Code) ||
  31. string.IsNullOrWhiteSpace(request.RedirectUri))
  32. {
  33. RecordTokenEvent(
  34. AuditEventType.SessionLoginFailure,
  35. "anonymous",
  36. "authentication/token/exchange",
  37. "invalid request");
  38. return BadRequest();
  39. }
  40. try
  41. {
  42. var tokens = await _tokenClient.ExchangeAuthorizationCodeAsync(
  43. request.Code,
  44. request.RedirectUri,
  45. cancellationToken);
  46. RecordTokenEvent(
  47. AuditEventType.SessionLogin,
  48. ExtractSubject(tokens.AccessToken),
  49. "authentication/token/exchange",
  50. "success");
  51. return Ok(tokens);
  52. }
  53. catch (KeycloakTokenRequestException exception)
  54. {
  55. RecordTokenEvent(
  56. AuditEventType.SessionLoginFailure,
  57. "anonymous",
  58. "authentication/token/exchange",
  59. $"keycloak rejected request: {(int)exception.StatusCode}");
  60. return Unauthorized(CreateTokenExchangeProblem(exception));
  61. }
  62. }
  63. [HttpPost("refresh")]
  64. public async Task<ActionResult<AuthTokenSetResponse>> Refresh(
  65. [FromBody] RefreshTokenRequest request,
  66. CancellationToken cancellationToken)
  67. {
  68. if (string.IsNullOrWhiteSpace(request.RefreshToken))
  69. {
  70. RecordTokenEvent(
  71. AuditEventType.SessionRefreshFailure,
  72. "anonymous",
  73. "authentication/token/refresh",
  74. "invalid request");
  75. return BadRequest();
  76. }
  77. try
  78. {
  79. var tokens = await _tokenClient.RefreshAccessTokenAsync(
  80. request.RefreshToken,
  81. cancellationToken);
  82. RecordTokenEvent(
  83. AuditEventType.SessionRefresh,
  84. ExtractSubject(tokens.AccessToken),
  85. "authentication/token/refresh",
  86. "success");
  87. return Ok(tokens);
  88. }
  89. catch (KeycloakTokenRequestException exception)
  90. {
  91. RecordTokenEvent(
  92. AuditEventType.SessionRefreshFailure,
  93. "anonymous",
  94. "authentication/token/refresh",
  95. $"keycloak rejected request: {(int)exception.StatusCode}");
  96. return Unauthorized(CreateTokenExchangeProblem(exception));
  97. }
  98. }
  99. private void RecordTokenEvent(
  100. string eventType,
  101. string actorIdentity,
  102. string resource,
  103. string outcome)
  104. {
  105. _auditService.Record(new AuditEvent(
  106. eventType,
  107. actorIdentity,
  108. resource,
  109. outcome,
  110. HttpContext.TraceIdentifier,
  111. DateTimeOffset.UtcNow));
  112. }
  113. private static string ExtractSubject(string accessToken)
  114. {
  115. try
  116. {
  117. var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
  118. return jwt.Claims.FirstOrDefault(claim =>
  119. claim.Type == JwtRegisteredClaimNames.Sub ||
  120. claim.Type == ClaimTypes.NameIdentifier ||
  121. claim.Type == ClaimTypes.Name ||
  122. claim.Type == "preferred_username")
  123. ?.Value ?? "unknown";
  124. }
  125. catch
  126. {
  127. return "unknown";
  128. }
  129. }
  130. private object CreateTokenExchangeProblem(KeycloakTokenRequestException exception)
  131. {
  132. if (!_environment.IsDevelopment())
  133. {
  134. return new { error = "Keycloak token request failed." };
  135. }
  136. return new
  137. {
  138. error = "Keycloak token request failed.",
  139. statusCode = (int)exception.StatusCode,
  140. keycloakResponse = exception.ResponseBody,
  141. };
  142. }
  143. }
  144. public sealed record AuthorizationCodeExchangeRequest(string Code, string RedirectUri);
  145. public sealed record RefreshTokenRequest(string RefreshToken);

Powered by TurnKey Linux.