mcdaniel.ws.AspNetCore.Authentication.SASToken 2.2.2

dotnet add package mcdaniel.ws.AspNetCore.Authentication.SASToken --version 2.2.2
                    
NuGet\Install-Package mcdaniel.ws.AspNetCore.Authentication.SASToken -Version 2.2.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="mcdaniel.ws.AspNetCore.Authentication.SASToken" Version="2.2.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="mcdaniel.ws.AspNetCore.Authentication.SASToken" Version="2.2.2" />
                    
Directory.Packages.props
<PackageReference Include="mcdaniel.ws.AspNetCore.Authentication.SASToken" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add mcdaniel.ws.AspNetCore.Authentication.SASToken --version 2.2.2
                    
#r "nuget: mcdaniel.ws.AspNetCore.Authentication.SASToken, 2.2.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package mcdaniel.ws.AspNetCore.Authentication.SASToken@2.2.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=mcdaniel.ws.AspNetCore.Authentication.SASToken&version=2.2.2
                    
Install as a Cake Addin
#tool nuget:?package=mcdaniel.ws.AspNetCore.Authentication.SASToken&version=2.2.2
                    
Install as a Cake Tool

Authentication.SASToken

Authentication library to protect endpoints using Shared Access Signatures (SASToken)

SASTokens can be composed of unique identifier, version, roles [or permissions], signature, expiration, start time, resource, ip [or range] and/or scheme. Signatures are generated from a secret using HMACSHA256 where the version describes the information used to populate the signature data. In the simplest form, the signature includes the Uri and the expiration of the token. The Id specifies which secret to use to generate the signature. When validating, the server will take it's own information about the request and generate a signature to compare with what the client has sent. Roles, resource, allowed ip addresses, and schemes can be used to add additional security to endpoints.

Tokens are verified using a SASTokenKey which contains all the properties of a SASToken with the addition of the Uri and secret used to generate the signature. Generated secrets are 32 bytes, base64 encoded.

This implementation allows for tokens to be created using wildcard verification of the url path given at runtime using either header or query string.

Url Authentication Example
https://example.com/api/get-user?api-version=2024-01&sp=https%3A%2F%2Fexample.com%2Fapi%2F**&sig=yjqFaDKxaVBXLhNBIxl%2FhFjVJeEe1UUIzI%2F28LtdJ0U%3D&se=1716400963&skn=99333392-1132-402a-838e-b4962b05c67e
Header Authentication Example
GET https://example.com/api/get-user HTTP/1.1
Host: example.com
Authorization: SharedAccessSignature  api-version=2024-01&sp=https%3A%2F%2Fexample.com%2Fapi%2F**&sig=yjqFaDKxaVBXLhNBIxl%2FhFjVJeEe1UUIzI%2F28LtdJ0U%3D&se=1716400963&skn=99333392-1132-402a-838e-b4962b05c67e

Rollover Guidance

In many cases, it best to add at least 2 SASTokenKeys for rollover purposes. When you want to expire SASTokens, you can use the secondary SASTokenKey to issue updated tokens to clients. After all clients have been updated, simply remove the old SASTokenKey and add another for a future rollover.

Wildcard paths

Path validation supports a wildcard character of an asterisk *. Single asterisk mean anything for a single segment, where as double asterick ** means match across one or more segments. Path matching is case-insensitive.

Matching Examples

url request: /segment1/segment2/segment3

matching SASTokenKey.Uri

/segment1/segment2/segment3 - exact path match only
/segment1/segment2/segment* - match root segment '/segment1/segment2/' and 3rd segment must starts wit 'segment' (only 3 segments allowed)
/seg** - match any path starting with 'seg' ('/seg/' included)
/** - match anything under root '/'
/**/segment3 - match all paths that end with 'segment3'
/*/*/segment3 - match any 2 segment names and ends with 'segment3' (only 3 segments allowed)
/segment1/** - match any endpoints under '/segment1' - (at least 1 non-zero length sub-segment is required)
/segment1/*/* - match root '/segment1' and require 2 non-zero length sub-segments, (only 3 segments allowed)
/segment1/segment2/* - match root segments '/segment1/segment2/' with any non-zero length 3rd segment (only 3 segments allowed)
/s*/*2/*me* - first segment must start with 's', second segment must end with '2', and third segment must contain the word 'me' (only 3 segments allowed)

Configuration

Default implementation includes SASTokenKey Store for in-memory and app-configuration support, but it is extensible for other persistence.

Using App Configuration

Configuration in appsettings.json

Add the following to your appsettings.json for a SASTokenKey. This will be used to verify signatures. change the path to the url you wish to restrict

"SASTokenKeys": {
        "99333392-1132-402a-838e-b4962b05c67e" : {
                "description":"Example",
                "path":"https://example.com/api/**",
                "version":"2024-01",
                "secret":"KBpx2E2FH/WM2hEuDr82m0OyDyscyGcvU/4Zn40AOFQ=",
                "expire":"0.00:05:00",
                "resource":"users",
                "ip":"0.0.0.0/0",
                "protocol":"https"
        }
}

Add the following to your program.cs (or startup.cs)

services.AddSASTokenStore_AppConfiguration();

Using In-Memory Configuration

Add the following to your program.cs (or startup.cs)

builder.Services.AddSASTokenStore_InMemory();
var app = builder.Build();
app.UseSASTokenStore_InMemory((services,tokenStore)=>{
	tokenStore.SaveAsync(new SASTokenKey() {
		Id = "99333392-1132-402a-838e-b4962b05c67e",
		Name = "Example",
		Secret = "KBpx2E2FH/WM2hEuDr82m0OyDyscyGcvU/4Zn40AOFQ=",
		Uri = new Uri("https://example.com/api/**"),
		Version = SASTokenKey.VERSION_ABSOLUTE_URI,
		Expiration = TimeSpan.FromMinutes(5.0),
		Resource = "users",
		AllowedIPAddresses = "192.168.1.10",
		Protocol = "https,http"
	}).Result;
});

Using Files for key storage

Add the following to your program.cs (or startup.cs) Uses IDataProtectionProvider (if configured) for secrets

services.AddDataProtection();
services.AddSASTokenStore_File(options => {
	options.BasePath = "~/secrets"; // supports full path or default of "~/" (IWebHostEnvironment.ContentRootPath)
	//options.FileNameFormat = "{Id}.json"; // also support properties ""{Description}\{Id}.json"
	//options.SearchPattern = "*.json"; // should match FileNameFormat
	//options.PreCache = true; // cache in-memory cache onload/save
	//options.SlidingCacheTime = TimeSpan.Zero; // indefinite in-memory caching
	//options.RemoveEmptyFolders = true; // used with folder in FileNameFormat - will remove empty folders
	//options.DefaultKeyName = Guid.Empty.ToString(); // default key name where Id is empty
});

Generating Tokens

Included is a console application Authentication.SASToken.Generator to generate new SASTokenKeys and SASTokens. Running the console will allow you to create the required configuration and a valid SASToken for authentication.

Running the Generator

C:\>Authentication.SASToken.Generator.exe
It is recommended to use a Guid for SASToken Ids.
  - A blank Id will create a new Guid id.
Enter SASTokenKey Id: 99333392-1132-402a-838e-b4962b05c67e
Enter a short description for the SASTokens: Example
Enter the Secret used to generated the SASToken signature.
  - Leave blank to generate a new secret
Enter Secret: KBpx2E2FH/WM2hEuDr82m0OyDyscyGcvU/4Zn40AOFQ=
Enter a relative or absolute url that this token will be valid for.
  - Wildcards are acceptable for path validation.
  - A blank url will allow all hosts and paths.
Enter url: https://example.com/api/**
Enter the version for the signature generation.  Leave blank to use default based on Uri
Allowed Versions:
        2024-04 = full uri in signature (Default)
        2024-05 = host only in signature
        2024-06 = uses a relative uri in the signature
Enter Version:
Using Version: 2024-04
Enter an expiration timespan that default tokens generated with this TokenSource will only be valid for.  Leave blank for max
Enter expiration timespan (d.HH:mm:ss): 0.00:05:00
This key can optionally restrict SASTokens by requiring a resource name. Leave blank to accept any value.
Enter the resource names (comma separated) that this key will protect: users
This key can optionally restrict SASTokens by requiring a scheme (ex. http,https.) Leave blank to accept any protocol.
Individual SASTokens can also further restrict these protocols.
Enter the protocol(s) - (comma separated) this key will allow: https
This key can optionally restrict SASTokens by only allowing certain ip address ranges. Comma separate for more than one range. formats:
  1.2.3.4  (single ip address)
  1.2.3.4/CIDR  (IP Address range using CIDR)
  1.2.3.0-1.2.3.255  (ip address range)
Individual SASTokens can also optionally include and override this range.
Enter the IP Address (or range) this key will allow: ::/0
appsettings.json format:
"SASTokenKeys": {
        "99333392-1132-402a-838e-b4962b05c67e" : {
                "description":"Example",
                "path":"https://example.com/api/**",
                "version":"2024-04",
                "secret":"KBpx2E2FH/WM2hEuDr82m0OyDyscyGcvU/4Zn40AOFQ=",
                "expire":"00:05:00",
                "resource":"users",
                "ip":"::/0",
                "protocol":"https"
        }
}
Roles are applied to a specific token and can be used during authentication. (not required)
Enter list of comma separated roles: Read,Write
The token requires a resource name in the authentication token. Valid resource names are:
  - users
Enter resource for the SASToken: users
Default Token: sv=2024-04&sr=users&sp=Read%2CWrite&sig=%2Fh6cXbnswIU6ur0UXrIDWwfQ1ru3Wfg7v5tM6KnGo1s%3D&se=1717010687&skn=99333392-1132-402a-838e-b4962b05c67e&spr=https&sip=%3A%3A%2F0
Enter a url to validate token (press enter to exit)
Url: https://example.com/api/get-user
Token Validated
...

You can copy and paste the appsettings.json format into your user secrets for later SASToken generation.

Adding Authentication to Endpoints

There are several ways to protect endpoints using SASTokens.

When a SASToken is authenticated using attributes or configuration, the HttpContext User is set to a ClaimsPrincipal that includes the details about the SASTokenKey used as well as roles encoded in the signature.

Issued Claims
Type Value Cardinality
ClaimTypes.NameIdentifier SASTokenKey.Id 1..1
ClaimTypes.Uri SASTokenKey.Uri 1..1
ClaimTypes.Version SASToken.Version 1..1
ClaimTypes.Expiration SASToken.Expiration.ToUnixTimeSeconds() 1..1
ClaimTypes.System SASToken.Resource 1..N (comma separated)
ClaimTypes.Role SASToken.Roles.Split(',') 0..N

After adding the configuration, you can:

Protect endpoints or entire controllers via attribute

// Allow all SASTokens matching - route must match SASTokenKey.Uri
[SASTokenAuthorization]
public class MyProtectedController() { ... } 

// Allow Admins or PowerUsers
[SASTokenAuthorization(new string[] { "Admin", "PowerUser" })]
public IActionResult GetUsers() => _impl.GetUsers().ToClientModel(); 

// Protect specific resource - requires token with id as resource.
[SASTokenAuthorization]
public IActionResult GetUser([SASTokenResource]Guid id) => _impl.GetUser(id).ToClientModel(); 

Protect entire paths via configuration in program.cs (or startup.cs)

services.AddAuthentication().AddSASToken(options => {...});

Inline Validation

Please note that inline validation does not assign the HttpContext.User

// FROM HEADER
public async Task<IActionResult> GetUsersAsync([FromServices] ISASTokenKeyStore store)
{
	if (!await store.ValidateAsync(HttpContext)) return Forbid();
	return Json((await _sdk.GetUsersAsync()).ToClientModel()); 
}

// FROM QUERY STRING
public async Task<IActionResult> GetUsersAsync([FromServices] ISASTokenKeyStore store, [FromQuery(Name = "sv")] string v, [FromQuery] string sig, [FromQuery] long se, [FromQuery] string skn, [FromQuery] string? sp = null, [FromQuery] string? sip = null, [FromQuery] string? sr = null, [FromQuery] string? spr = null, [FromQuery] long st = 0)
{
	var token = new SASToken()
	{
		Id = skn,
		Expiration = DateTimeOffset.FromUnixTimeSeconds(se),
		Signature = sig,
		Roles = sp,
		Version = v,
		AllowedIPAddresses = sip,
		Protocol = spr,
		Resource = sr,
		StartTime = st == 0 ? DateTimeOffset.MinValue : DateTimeOffset.FromUnixTimeSeconds(st)
	};
	string[] anyUserInRoles = new string[] { "Admin", "PowerUsers" };
	var tokenKey = await _tokenStore.GetAsync(token);
	if (!tokenKey?.Validate(token, Request, anyUserInRoles, null, HttpContext.Connection.RemoteIpAddress, _logger) ?? false) return Forbid();

	return Json((await _sdk.GetUsersAsync()).ToClientModel()); 
}

Product Compatible and additional computed target framework versions.
.NET 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.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.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
2.2.2 122 12/12/2025
2.2.1 201 9/18/2024
2.2.0 197 9/16/2024
2.1.1 161 6/13/2024
2.1.0 153 6/13/2024
2.0.0 160 5/29/2024
1.0.3 164 5/24/2024
1.0.2 161 5/23/2024