Skip to content

Latest commit

 

History

History
163 lines (117 loc) · 4.05 KB

File metadata and controls

163 lines (117 loc) · 4.05 KB

UMS .NET SDK — Quickstart

Language: English | Español

Get a method protected by UMS authorization in five minutes. Assumes you already have a UMS tenant and can call POST /api/v1/client/authenticate.

For full reference, see README.md.


Step 1 — Install packages

dotnet add package Ums.Sdk.Authorization
dotnet add package Ums.Sdk.Authorization.Aop

For tests:

dotnet add package Ums.Sdk.Authorization.Testing

Step 2 — Register services

In Program.cs:

using Ums.Sdk.Authorization;
using Ums.Sdk.Authorization.Aop;
using BeyondNetCode.Shell.DI;

builder.Services.AddAop(aop => aop
    .AddAspect<AuthorizationAspect>());

builder.Services.AddUmsSdkAuthorization();          // validator + default accessor
builder.Services.AddHttpContextAuthGraphAccessor(); // ASP.NET Core integration

Step 3 — Populate the graph per request

Add the middleware that decodes the JWT body and stores the graph in HttpContext.Items:

app.UseUmsAuthGraph();   // before UseAuthorization

This middleware:

  1. Reads the Authorization: Bearer ... header.
  2. Calls POST /api/v1/client/authenticate if no cached graph or the cached one expired.
  3. Stores the parsed AuthorizationGraph in HttpContext.Items["UmsAuthGraph"].
  4. Validates schemaVersion against the SDK's compatibility range; rejects with 401 on AUTH_205.

Step 4 — Protect a method

Add an attribute to the interface method:

public interface IOrderService
{
    [RequiresScope("PURCHASE_ORDER.APPROVE")]
    Task<Result> ApproveOrderAsync(Guid orderId);
}

public class OrderService : IOrderService
{
    public async Task<Result> ApproveOrderAsync(Guid orderId)
    {
        // your business logic — runs only if authorized
        return Result.Success();
    }
}

Register the AOP-proxied service:

builder.Services.AddAopProxy<IOrderService, OrderService>();

That's it. IOrderService.ApproveOrderAsync is now protected. Calling it from an endpoint or another service:

app.MapPost("/orders/{id}/approve", async (Guid id, IOrderService svc) =>
{
    var result = await svc.ApproveOrderAsync(id);
    return result.IsSuccess ? Results.Ok() : Results.Forbid();
});

If the authenticated user lacks PURCHASE_ORDER.APPROVE, ApproveOrderAsync throws UnauthorizedAccessException before the body runs.


Step 5 — Test it

[Fact]
public async Task ApproveOrder_WithoutScope_ReturnsForbidden()
{
    var graph = AuthGraphBuilder
        .ForTenant("LOGISTICS_CORE")
        .WithUser("ana.flores@example.com")
        .WithScope("PURCHASE_ORDER.VIEW")   // VIEW but not APPROVE
        .Build();

    var accessor = new TestAuthGraphAccessor(graph);
    var service = AopProxyCreator.Create<IOrderService, OrderService>(
        new OrderService(),
        TestAspectExecutorFactory.Create(accessor));

    await Assert.ThrowsAsync<UnauthorizedAccessException>(
        () => service.ApproveOrderAsync(Guid.NewGuid()));
}

Common adjustments

Return Result.Failure instead of throwing

[RequiresScope("PURCHASE_ORDER.APPROVE", OnDenied = DenialBehavior.ReturnFailure)]
Task<Result> ApproveOrderAsync(Guid orderId);

Roll out gradually with audit-only mode

builder.Services.Configure<AuthorizationOptions>(o =>
    o.Mode = AuthorizationMode.AuditOnly);

Denials are logged but not blocked. Watch logs for AuthorizationDeniedEvent entries, fix gaps, then switch to Enforce.

Check a permission imperatively (no attribute)

public class OrderController(IAuthorizationValidator validator, IAuthGraphAccessor accessor)
{
    public IActionResult Approve(Guid id)
    {
        var decision = validator.RequireScope(accessor.Current, "PURCHASE_ORDER.APPROVE");
        if (decision.IsDenied) return Forbid();
        // ...
    }
}

Next Steps