Private/New-GovUKNotifyJwt.ps1

function New-GovUKNotifyJwt {
    <#
        .SYNOPSIS
        Creates a short-lived JSON Web Token for GOV.UK Notify API authentication.

        .DESCRIPTION
        GOV.UK Notify authenticates requests with a JWT (HS256) carried in the Authorization header.
        The token's 'iss' claim is the service id and it is signed with the secret key, both of which
        are embedded in the API key using the format '{key_name}-{service_id}-{secret_key}', where
        service_id and secret_key are each UUIDs.

        The token's 'iat' (issued at) claim is the current UTC time in epoch seconds. GOV.UK Notify
        rejects tokens whose 'iat' is more than 30 seconds from its own clock, so a fresh token is
        generated for every request.

        This is an internal helper and is not exported by the module.

        .PARAMETER ApiKey
        The GOV.UK Notify API key, in the format '{key_name}-{service_id}-{secret_key}'.

        .OUTPUTS
        System.String. The encoded JWT.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Generates a token value in memory; it does not change any system state.')]
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ApiKey
    )

    # -- The key name may itself contain hyphens, so parse the two UUIDs from the end of the key.
    $Parts = $ApiKey -split '-'
    if ($Parts.Count -lt 11) {
        throw "Invalid GOV.UK Notify API key format. Expected '{key_name}-{service_id}-{secret_key}', where service_id and secret_key are UUIDs."
    }

    $SecretKey = ($Parts[-5..-1]) -join '-'
    $ServiceId = ($Parts[-10..-6]) -join '-'

    # -- Build the JWT header and payload.
    $Header = [ordered]@{ typ = 'JWT'; alg = 'HS256' } | ConvertTo-Json -Compress
    $Payload = [ordered]@{
        iss = $ServiceId
        iat = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
    } | ConvertTo-Json -Compress

    $EncodedHeader = ConvertTo-GovUKNotifyBase64Url -Bytes ([System.Text.Encoding]::UTF8.GetBytes($Header))
    $EncodedPayload = ConvertTo-GovUKNotifyBase64Url -Bytes ([System.Text.Encoding]::UTF8.GetBytes($Payload))
    $SigningInput = "$EncodedHeader.$EncodedPayload"

    # -- Sign with HMAC-SHA256 using the secret key.
    $Hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes($SecretKey))
    try {
        $SignatureBytes = $Hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($SigningInput))
    }
    finally {
        $Hmac.Dispose()
    }
    $EncodedSignature = ConvertTo-GovUKNotifyBase64Url -Bytes $SignatureBytes

    return "$SigningInput.$EncodedSignature"
}