Skip to content

Commit

Permalink
add middleware and builder extension method for usage
Browse files Browse the repository at this point in the history
  • Loading branch information
Friedemann Braune committed Jan 7, 2024
1 parent 2062b24 commit aeef9ac
Showing 1 changed file with 68 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
namespace Keycloak.AuthServices.Authorization;

using Keycloak.AuthServices.Sdk.AuthZ;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;

/// <summary>
/// Policy Enforcment Point, which automatically protects request URLs by requesting permission at the keycloak authorization API.
/// Using this, there is no need to:
/// a) register policies in code (as the policies provided in keycloak will be used for authentication)
/// b) annotate controller methods with the "[Authorize]" attribute
/// It is possible to exclude methods from the policy enforcement by annotate them with the "[AllowAnonymous]" attribute, however.
/// </summary>
public class AutoProtectResourceMiddleware
{
private readonly RequestDelegate next;
private readonly IKeycloakProtectionClient client;

/// <summary>
/// <see cref="AutoProtectResourceMiddleware"/>
/// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> to proceed with in case of authorization success.</param>
/// <param name="client">The <see cref="IKeycloakProtectionClient"/> is used for the authorization request.</param>
/// <exception cref="ArgumentNullException">Thrown, if <see cref="IKeycloakProtectionClient"/> is null.</exception>
public AutoProtectResourceMiddleware(RequestDelegate next, IKeycloakProtectionClient client)
{
this.next = next;
this.client = client ?? throw new ArgumentNullException(nameof(client));
}

/// <inheritdoc/>
public async Task InvokeAsync(HttpContext context)
{
var targetAllowsAnonymous = context.Features?
.Get<IEndpointFeature>()?
.Endpoint?
.Metadata
.Any(attribute => attribute is AllowAnonymousAttribute) ?? false;

if (!targetAllowsAnonymous)
{
var isAuthorized = await this.client.VerifyAccessToResource(
context.Request.Path, context.Request.Method, CancellationToken.None);

if (!isAuthorized)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
}

await this.next(context);
}
}

/// <summary/>
public static class MiddlewareExtensions
{
/// <summary>
/// Extension method to enable automatic resource protection.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/> isntance.</param>
/// <returns>The <see cref="IApplicationBuilder"/> isntance with <see cref="AutoProtectResourceMiddleware"/> usage.</returns>
public static IApplicationBuilder UseAutomaticKeycloakEndpointProtection(
this IApplicationBuilder builder) => builder.UseMiddleware<AutoProtectResourceMiddleware>();
}

0 comments on commit aeef9ac

Please sign in to comment.