Tingle.AspNetCore.Tokens
4.14.1
dotnet add package Tingle.AspNetCore.Tokens --version 4.14.1
NuGet\Install-Package Tingle.AspNetCore.Tokens -Version 4.14.1
<PackageReference Include="Tingle.AspNetCore.Tokens" Version="4.14.1" />
paket add Tingle.AspNetCore.Tokens --version 4.14.1
#r "nuget: Tingle.AspNetCore.Tokens, 4.14.1"
// Install Tingle.AspNetCore.Tokens as a Cake Addin #addin nuget:?package=Tingle.AspNetCore.Tokens&version=4.14.1 // Install Tingle.AspNetCore.Tokens as a Cake Tool #tool nuget:?package=Tingle.AspNetCore.Tokens&version=4.14.1
Tingle.AspNetCore.Tokens
This library adds support for generation of continuation tokens in ASP.NET Core with optional expiry. This is particularly useful for pagination, user invite tokens, expiring operation tokens, etc.
The functionality is availed through the ContinuationToken<T>
and TimedContinuationToken<T>
types. These are backed using the DataProtection sub-system in ASP.NET Core.
See sample.
First step is to register the required services.
var builder = WebApplication.CreateBuilder(args);
// see https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-8.0
builder.Services.AddDataProtection();
builder.Services.AddControllers()
.AddTokens();
var app = builder.Build();
app.MapControllers();
app.Run();
Pagination
Pagination is best served using ContinuationToken<T>
with DateTimeOffset
or an incrementing identifier in your database.
[ApiController]
[Route("/books")]
[ProducesErrorResponseType(typeof(ValidationProblemDetails))]
public class BooksController : ControllerBase
{
static readonly List<Book> Books = new();
[HttpGet]
public IActionResult List([FromQuery] ContinuationToken<DateTimeOffset>? token)
{
var last = token?.GetValue();
var query = last is not null ? Books.Where(b => b.Created > last) : Books;
query = query.Take(10); // limit the number of items to pull from the database
var books = query.ToList(); // pull from the database
last = books.Any() ? books.Last().Created : null;
if (last is not null)
{
var ct = new ContinuationToken<DateTimeOffset>(last.Value);
return this.Ok(books, ct);
}
return Ok(books);
}
}
User invitation and account transfer tokens
User invitation tokens that do not expire are best served using ContinuationToken<T>
with a custom model inside that can carry extra information with it. The same can be done for other scenarios such as account transfer and email validation.
[ApiController]
[Route("/users")]
[ProducesErrorResponseType(typeof(ValidationProblemDetails))]
public class UsersController : ControllerBase
{
private readonly ITokenProtector<InvitationLinkToken> tokenProtector;
public UsersController(ITokenProtector<InvitationLinkToken> tokenProtector)
{
this.tokenProtector = tokenProtector ?? throw new ArgumentNullException(nameof(tokenProtector));
}
[HttpPost]
public IActionResult Invite([FromBody] UserCreateModel model)
{
// create the invitation in the database
var invitationId = Guid.NewGuid().ToString();
// send invite email
var token = tokenProtector.Protect(new InvitationLinkToken { Id = invitationId, });
// send email using the token
return Ok();
}
[HttpPost("{id}/resend")]
public IActionResult Resend([FromRoute, Required] string id)
{
// find the invitation from the database
var invitationId = Guid.NewGuid().ToString();
// resend invite email
var token = tokenProtector.Protect(new InvitationLinkToken { Id = invitationId, });
// send email using the token
return Ok();
}
}
[ApiController]
[Route("/invites")]
[ProducesErrorResponseType(typeof(ValidationProblemDetails))]
public class InvitesController : ControllerBase
{
[HttpPost("accept")]
public IActionResult Accept([FromQuery, Required] ContinuationToken<InvitationLinkToken> token)
{
// ensure the invite exists
var model = token.GetValue();
var invitationId = model.Id;
// do the database magic here
return Ok();
}
[HttpPost("reject")]
public IActionResult Reject([FromQuery, Required] ContinuationToken<InvitationLinkToken> token)
{
// ensure the invite exists
var model = token.GetValue();
var invitationId = model.Id;
// do the database magic here
return Ok();
}
}
Expiration
For certain scenarios, expiration is desired. Such as confirmation of monetary disbursement. In such, you should use TimedContinuationToken<T>
when receiving the token and pass an absolute/relative expiration when protecting the data.
[ApiController]
[Route("/disbursement")]
[ProducesErrorResponseType(typeof(ValidationProblemDetails))]
public class DisbursementController : ControllerBase
{
private readonly ITokenProtector<DisbursementToken> tokenProtector;
public DisbursementController(ITokenProtector<DisbursementToken> tokenProtector)
{
this.tokenProtector = tokenProtector ?? throw new ArgumentNullException(nameof(tokenProtector));
}
[HttpPost("initiate")]
public IActionResult Initiate([FromBody] DisbursementInitiateModel model)
{
// do light weight checks e.g. enough funds in the account
// generate token and send it back in the response
var ttl = TimeSpan.FromMinutes(1);
var token = tokenProtector.Protect(new DisbursementToken { Amount = model.Amount, Iban = model.Iban, }, ttl);
// to confirm, the user will call /confirmed?token={token}
return Ok(new DisbursementResponseModel { Token = token, });
}
[HttpPost("confirmed")]
public IActionResult Confirmed([FromQuery, Required] TimedContinuationToken<DisbursementToken> token)
{
// do light weight checks that may have changed e.g. enough funds in the account
// do the disbursement here (if expired, we never get here)
var model = token.GetValue();
var amount = model.Amount;
var iban = model.Iban;
return Ok();
}
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- No dependencies.
-
net7.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
4.14.1 | 170 | 10/14/2024 |
4.14.0 | 344 | 9/16/2024 |
4.13.0 | 626 | 8/13/2024 |
4.12.0 | 168 | 8/7/2024 |
4.11.2 | 309 | 7/15/2024 |
4.11.1 | 359 | 6/26/2024 |
4.11.0 | 305 | 6/6/2024 |
4.10.1 | 104 | 6/5/2024 |
4.10.0 | 195 | 5/27/2024 |
4.9.0 | 325 | 5/16/2024 |
4.8.0 | 356 | 5/5/2024 |
4.7.0 | 552 | 3/25/2024 |
4.6.0 | 375 | 3/8/2024 |
4.5.0 | 2,224 | 11/22/2023 |
4.4.1 | 275 | 11/20/2023 |
4.4.0 | 247 | 11/15/2023 |
4.3.0 | 668 | 10/18/2023 |
4.2.2 | 804 | 9/20/2023 |
4.2.1 | 1,217 | 8/4/2023 |
4.2.0 | 1,615 | 5/31/2023 |
4.1.1 | 297 | 5/26/2023 |
4.1.0 | 291 | 5/22/2023 |
4.0.0 | 1,652 | 3/14/2023 |
2.5.0 | 2,458 | 11/21/2022 |
2.4.2 | 4,614 | 7/25/2022 |
2.4.1 | 4,821 | 3/22/2022 |
2.4.0 | 4,610 | 11/10/2021 |
2.3.1 | 2,892 | 9/20/2021 |
2.3.0 | 1,428 | 7/22/2021 |