6.6 Sign in, and run trains as the caller
Program.cs:
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Shopfront.Auth;
using Shopfront.Data;
using Shopfront.Extensions;
using Shopfront.Trains.PlaceOrder;
using Trax.Api.Auth;
using Trax.Api.Auth.Jwt;
using Trax.Api.Exceptions;
using Trax.Api.Extensions;
using Trax.Core.Exceptions;
using Trax.Effect.Data.Extensions;
using Trax.Effect.Data.Postgres.Extensions;
using Trax.Effect.Data.Services.DomainContext;
using Trax.Effect.Extensions;
using Trax.Effect.JunctionProvider.Logging.Extensions;
using Trax.Effect.Provider.Parameter.Extensions;
using Trax.Mediator.Extensions;
using Trax.Mediator.Services.TrainExecution;
var builder = WebApplication.CreateBuilder(args);
var connectionString =
builder.Configuration.GetConnectionString("Shopfront")
?? throw new InvalidOperationException("ConnectionStrings:Shopfront is not set.");
builder.Services.AddShopfront(connectionString);
if (builder.Environment.IsDevelopment())
builder.Services.AddTraxJwtAuth(jwt =>
jwt.UseSymmetricKey(DevJwt.Issuer, DevJwt.Audience, DevJwt.Key)
);
else
builder.Services.AddTraxJwtAuth(
builder.Configuration["Auth:Authority"]
?? throw new InvalidOperationException("Auth:Authority is not set."),
builder.Configuration["Auth:Audience"]
?? throw new InvalidOperationException("Auth:Audience is not set.")
);
builder.Services.AddAuthorization();
builder.Services.AddTrax(trax =>
trax.AddEffects(effects =>
effects
.UsePostgres(connectionString)
.SaveTrainParameters()
.AddJunctionLogger(serializeJunctionData: true)
.AddDecisionRecording()
)
.AddMediator(typeof(Program).Assembly)
);
builder.Services.AddTraxApi();
var app = builder.Build();
await app.Services.EnsureSchemaCreatedAsync<ShopfrontDbContext>();
app.UseAuthentication();
app.UseAuthorization();
app.MapPost(
"/orders",
async (PlaceOrderInput input, ITrainExecutionService trains, CancellationToken ct) =>
{
try
{
var run = await trains.RunAsync(
typeof(IPlaceOrderTrain).FullName!,
JsonSerializer.Serialize(input),
ct
);
return Results.Ok(run.Output);
}
catch (TrainAuthorizationException)
{
return Results.StatusCode(StatusCodes.Status403Forbidden);
}
catch (TrainException ex)
{
return Results.UnprocessableEntity(ex.Message);
}
}
)
.RequireAuthorization(TraxAuthClaimTypes.TraxAuthPolicy);
app.MapGet(
"/orders",
async (IShopfrontDbContext db, CancellationToken ct) =>
await db
.Orders.OrderBy(o => o.PlacedAt)
.Select(o => new
{
o.Id,
o.CustomerId,
o.Total,
o.PlacedAt,
})
.ToListAsync(ct)
)
.RequireAuthorization(TraxAuthClaimTypes.TraxAuthPolicy);
if (app.Environment.IsDevelopment())
app.MapGet(
"/dev/token/{user}",
(string user, bool staff = false) =>
DevJwt.Mint(user, staff ? Roles.Staff : Roles.Customer)
)
.AllowAnonymous();
await app.RunAsync();From the top:
AddTraxJwtAuthregisters the JWT scheme asTraxJwt, and the services that turn a signed-in caller intoTraxPrincipalandTraxCaller. In Development it checks tokens against the development key; anywhere else against an identity provider, read from configuration. Nothing else changes between the two, because the rest of the application sees only the principal.AddTraxApiregisters the service that checks[TraxAuthorize]. Leave it out and the host does not start; that is below.RequireAuthorization(TraxAuthClaimTypes.TraxAuthPolicy)on each endpoint signs the caller in with Trax's schemes and refuses anyone who is not, with a401. The Trax scheme is never the application's default, so an endpoint that says nothing signs nobody in.POST /ordersrunsPlaceOrderthroughITrainExecutionService, by its interface's name and with its input as JSON. That is the path that checks[TraxAuthorize]: a member of staff is signed in, so passes the endpoint, and is refused by the train with a403.GET /ordersqueries the orders. It has noWhere: the query filter is the rule./dev/token/{user}mints a token, in Development only. Outside it, the route does not exist.
SDK Reference
AddTraxJwtAuth | TraxAuthorize | TraxPrincipal | ITrainExecutionService | Scope a data context to the caller | Effect data testing
Next: 6.7 Try it