Add per-envelope JWT authentication and validation
Introduced a new `EnvelopeReceiverJwt` authentication scheme to support per-envelope JWT validation using cookies specific to envelope keys. Added the `CheckEnvelopeReceiver` endpoint in `AuthController.cs` to validate these tokens, protected by the `AuthPolicy.Receiver` policy. Configured the `EnvelopeReceiverJwt` scheme to dynamically resolve issuer signing keys and validate tokens. Enhanced `JwtBearerEvents.OnMessageReceived` to extract envelope keys from the request path and retrieve tokens from corresponding cookies. Updated the `AuthPolicy.Receiver` policy to use the `EnvelopeReceiverJwt` scheme, ensuring isolated authentication for per-envelope scenarios. Added XML documentation for the `CheckEnvelopeReceiver` method.
This commit is contained in:
@@ -73,4 +73,17 @@ public partial class AuthController(IOptions<AuthTokenKeys> authTokenKeyOptions,
|
||||
=> role is not null && !User.IsInRole(role)
|
||||
? Unauthorized()
|
||||
: Ok();
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the caller holds a valid per-envelope receiver token for the given envelope key.
|
||||
/// The request must carry a cookie named <c>AuthTokenSignFLOWReceiver.{envelopeKey}</c>.
|
||||
/// </summary>
|
||||
/// <param name="envelopeKey">The unique envelope key extracted from the route.</param>
|
||||
/// <response code="200">Valid per-envelope token found.</response>
|
||||
/// <response code="401">Token is missing, expired or invalid.</response>
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[Authorize(Policy = AuthPolicy.Receiver)]
|
||||
[HttpGet("check/envelope/{envelopeKey}")]
|
||||
public IActionResult CheckEnvelopeReceiver([FromRoute] string envelopeKey) => Ok();
|
||||
}
|
||||
@@ -127,6 +127,9 @@ try
|
||||
|
||||
var authTokenKeys = config.GetOrDefault<AuthTokenKeys>();
|
||||
|
||||
// Scheme name used for per-envelope receiver JWT authentication.
|
||||
const string EnvelopeReceiverScheme = "EnvelopeReceiverJwt";
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
@@ -161,6 +164,48 @@ try
|
||||
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
|
||||
context.Token = queryStrToken;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
})
|
||||
// Per-envelope receiver scheme: reads the JWT from the cookie named
|
||||
// AuthTokenSignFLOWReceiver.{envelope_key} where envelope_key is the
|
||||
// last path segment of the request URL.
|
||||
// This enables simultaneous authentication for multiple envelopes
|
||||
// within the same browser session.
|
||||
.AddJwtBearer(EnvelopeReceiverScheme, opt =>
|
||||
{
|
||||
opt.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
|
||||
{
|
||||
var clientParams = deferredProvider.GetOptions<ClientParams>();
|
||||
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
|
||||
return [publicKey.SecurityKey];
|
||||
},
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = authTokenKeys.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = authTokenKeys.Audience,
|
||||
};
|
||||
|
||||
opt.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var paths = context.Request.Path.Value?.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// Derive the envelope key from the last route segment: /{envelope_key}
|
||||
var envelopeKey = paths?.LastOrDefault();
|
||||
|
||||
if (envelopeKey is not null)
|
||||
{
|
||||
var cookieName = CookieNames.GetEnvelopeReceiverCookieName(authTokenKeys.Cookie, envelopeKey);
|
||||
if (context.Request.Cookies.TryGetValue(cookieName, out var cookieToken) && cookieToken is not null)
|
||||
context.Token = cookieToken;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
@@ -183,8 +228,13 @@ try
|
||||
policy.RequireRole(Role.Sender, Role.Receiver.Full))
|
||||
.AddPolicy(AuthPolicy.Sender, policy =>
|
||||
policy.RequireRole(Role.Sender))
|
||||
// Per-envelope policy: uses the dedicated EnvelopeReceiverJwt scheme so it
|
||||
// never conflicts with the default JwtBearer scheme.
|
||||
.AddPolicy(AuthPolicy.Receiver, policy =>
|
||||
policy.RequireRole(Role.Receiver.Full, "receiver"))
|
||||
policy
|
||||
.AddAuthenticationSchemes(EnvelopeReceiverScheme)
|
||||
.RequireAuthenticatedUser()
|
||||
.RequireRole(Role.Receiver.Full, "receiver"))
|
||||
.AddPolicy(AuthPolicy.ReceiverTFA, policy =>
|
||||
policy.RequireRole(Role.Receiver.TFA));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user