Reference

CognitoTokenIssuer

Package Trax.Api.Auth.Jwt.Cognito.IssuerNamespace Trax.Api.Auth.Jwt.Cognito.IssuerSource CognitoTokenIssuer.cs, CognitoAccessTokenRequest.cs, CognitoIdTokenRequest.cs, FederatedIdentity.cs, TestJwksServerCognitoExtensions.cs

NO WARRANTY. Trax auth is plumbing, not a security product. You are solely responsible for securing systems that use it. See API Security.

Mints RS256 access and ID tokens with the claims Amazon Cognito user pools emit, for a local Cognito stand-in or for tests. It is the minting counterpart of Trax.Api.Auth.Jwt.Cognito, which validates such tokens; a host that only validates does not reference this package. Refresh tokens are on IRefreshTokenStore.

Signature

public sealed class CognitoTokenIssuer
{
    public CognitoTokenIssuer(string issuer, RsaSecurityKey signingKey, TimeProvider? clock = null);
 
    public string MintAccessToken(CognitoAccessTokenRequest request);
    public string MintIdToken(CognitoIdTokenRequest request);
}
 
public sealed record FederatedIdentity(
    string UserId,
    string ProviderName,
    string ProviderType,
    bool Primary,
    DateTimeOffset DateCreated);
 
public static class TestJwksServerCognitoExtensions
{
    public static CognitoTokenIssuer CreateCognitoIssuer(this TestJwksServer server, TimeProvider? clock = null);
}

Parameters

ParameterTypeDescription
issuerstringThe iss claim. Real Cognito uses https://cognito-idp.{region}.amazonaws.com/{userPoolId}.
signingKeyRsaSecurityKeyThe signing key. Its KeyId is written to the token header kid and must be in the JWKS the validator fetches. A blank KeyId throws ArgumentException.
clockTimeProvider?Sets iat, nbf, exp and the default auth_time. Defaults to TimeProvider.System.

Members

MemberDescription
MintAccessToken(request)An access token from a CognitoAccessTokenRequest.
MintIdToken(request)An ID token from a CognitoIdTokenRequest.
TestJwksServer.CreateCognitoIssuer(clock)An issuer that signs with a TestJwksServer's current key under its issuer URL, so its tokens validate against that server's JWKS. It ships in this package so that Trax.Api.Auth.Jwt.Testing does not depend on Cognito issuance.

CognitoAccessTokenRequest

PropertyRequiredDescription
SubyesGuid, written as sub.
ClientIdyesWritten as client_id. An access token has no aud.
LifetimeyesMust be positive.
UsernamenoWritten as username. Defaults to Sub.
ScopesnoJoined with spaces into scope. Omitted when empty.
GroupsnoOne cognito:groups claim each.
AuthTimenoWritten as auth_time. Defaults to the issue time.
Email, EmailVerified, GivenName, FamilyNamenoWritten as email, email_verified, given_name, family_name when set. An empty or whitespace Email throws. A real pool adds these to access tokens only through a PreTokenGeneration trigger.
AdditionalClaimsnoIReadOnlyDictionary<string, string>, written verbatim. A reserved name throws.

CognitoIdTokenRequest

PropertyRequiredDescription
SubyesGuid, written as sub.
ClientIdyesWritten as aud.
LifetimeyesMust be positive.
EmailyesWritten as email. Blank throws.
EmailVerifiedyesWritten as email_verified.
GivenName, FamilyNamenoOmitted when null or empty.
UsernamenoWritten as cognito:username. Defaults to Sub.
GroupsnoOne cognito:groups claim each.
IdentitiesnoFederatedIdentity entries, written as the identities JSON array.
AuthTimenoWritten as auth_time. Defaults to the issue time.
AdditionalClaimsnoAs on the access token request.

FederatedIdentity

One entry of the identities claim, serialized with camelCase keys.

PropertyDescription
UserIdThe provider's subject.
ProviderNameThe Cognito-side provider name (Google, SignInWithApple). CognitoJwtPrincipalResolver surfaces it as identity_provider.
ProviderTypeThe provider type: the provider name for OIDC federations, SAML for SAML.
PrimaryThe resolver prefers the primary entry.
DateCreatedWhen the identity was linked, serialized as epoch milliseconds.

Returns

MintAccessToken and MintIdToken return the signed token as a compact JWT string.

Behavior

ClaimAccess tokenID token
token_useaccessid
Audienceclient_id, no audaud
usernamealwaysnot written
cognito:usernamenot writtenalways
email, email_verifiedwhen setalways
given_name, family_namewhen setwhen set
identitiesnot writtenwhen set
scopewhen Scopes is not emptynot written
cognito:groupsone per groupone per group
auth_timeAuthTime, or the issue timeAuthTime, or the issue time
jtia new GUID per calla new GUID per call

A token minted here and validated through UseCognito and CognitoJwtPrincipalResolver gives the TraxPrincipal a real Cognito token with the same claims gives. A host that validates the standard aud claim (UseAuthority rather than UseCognito) accepts only the ID token.

Reserved claim names

AdditionalClaims refuses every name the issuer sets itself, and every name CognitoJwtPrincipalResolver reads an identity, a display name or roles from. Minting throws ArgumentException naming the claim:

sub, iss, aud, exp, nbf, iat, jti, auth_time, token_use, client_id, scope, username, cognito:username, cognito:groups, identities, email, email_verified, given_name, family_name, name, preferred_username, role, roles, the ClaimTypes.Role, ClaimTypes.Name and ClaimTypes.NameIdentifier URIs, trax:principal-id and trax:principal-type.

A host that forwards user-editable attributes into AdditionalClaims therefore cannot be made to mint a token with an extra group or another subject. A blank name is skipped.

Examples

using var rsa = RSA.Create(2048);
var key = new RsaSecurityKey(rsa) { KeyId = "local-pool-key-1" };
var issuer = new CognitoTokenIssuer("http://localhost:8080/local_us-east-1_xxx", key);
var userId = Guid.NewGuid();
 
var accessToken = issuer.MintAccessToken(new CognitoAccessTokenRequest
{
    Sub = userId,
    ClientId = "mobile-app-client-id",
    Lifetime = TimeSpan.FromHours(1),
    Username = "alice",
    Scopes = ["openid", "profile"],
    Groups = ["admin"],
});
 
var idToken = issuer.MintIdToken(new CognitoIdTokenRequest
{
    Sub = userId,
    ClientId = "mobile-app-client-id",
    Lifetime = TimeSpan.FromHours(1),
    Email = "alice@example.com",
    EmailVerified = true,
    Identities = [new FederatedIdentity("108222222222", "Google", "Google", Primary: true, DateCreated: DateTimeOffset.UtcNow)],
});

Paired with a test JWKS server:

await using var server = await TestJwksServer.StartAsync();
var issuer = server.CreateCognitoIssuer();
 
var token = issuer.MintAccessToken(new CognitoAccessTokenRequest
{
    Sub = Guid.NewGuid(),
    ClientId = "test-client",
    Lifetime = TimeSpan.FromHours(1),
});

See also