Is there any JSON Web Token (JWT) example in C#?

我觉得我在吃疯狂的药。通常,对于任何给定的任务,网络上总是有一百万个库和样本。我正在尝试使用描述为 给你的 JSON Web 令牌(JWT)来实现使用 Google“ Service Account”的身份验证。

然而,在 PHP、 Python 和 Java 中只有客户端库。即使搜索 Google 认证之外的 JWT 示例,也只能找到关于 JWT 概念的蟋蟀和草稿。这真的是一个如此新的、可能是谷歌专有的系统吗?

我能设法解释的最接近的 java 示例看起来非常密集和令人生畏。C # 里肯定有什么东西我至少可以从它开始。如果能帮上忙就太好了!

163662 次浏览

我找到了一个 JsonWebToken 的基本实现,并使用 Google 风格对其进行了扩展。我还没有完全搞清楚,但已经有97% 了。这个项目失去了动力,所以希望这能帮助其他人获得一个良好的开端:

注: 我对基本实现所做的更改(我不记得在哪里找到它了)是:

  1. 更改 HS256-> RS256
  2. 在头中交换了 JWT 和 alg 顺序。不知道谁搞错了,谷歌还是规格,但谷歌采取了这种方式,根据他们的文档如下。
public enum JwtHashAlgorithm
{
RS256,
HS384,
HS512
}
    

public class JsonWebToken
{
private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;


static JsonWebToken()
{
HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
{
{ JwtHashAlgorithm.RS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } },
{ JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } },
{ JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } }
};
}


public static string Encode(object payload, string key, JwtHashAlgorithm algorithm)
{
return Encode(payload, Encoding.UTF8.GetBytes(key), algorithm);
}


public static string Encode(object payload, byte[] keyBytes, JwtHashAlgorithm algorithm)
{
var segments = new List<string>();
var header = new { alg = algorithm.ToString(), typ = "JWT" };


byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
//byte[] payloadBytes = Encoding.UTF8.GetBytes(@"{"iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/prediction","aud":"https://accounts.google.com/o/oauth2/token","exp":1328554385,"iat":1328550785}");


segments.Add(Base64UrlEncode(headerBytes));
segments.Add(Base64UrlEncode(payloadBytes));


var stringToSign = string.Join(".", segments.ToArray());


var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);


byte[] signature = HashAlgorithms[algorithm](keyBytes, bytesToSign);
segments.Add(Base64UrlEncode(signature));


return string.Join(".", segments.ToArray());
}


public static string Decode(string token, string key)
{
return Decode(token, key, true);
}


public static string Decode(string token, string key, bool verify)
{
var parts = token.Split('.');
var header = parts[0];
var payload = parts[1];
byte[] crypto = Base64UrlDecode(parts[2]);


var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
var headerData = JObject.Parse(headerJson);
var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
var payloadData = JObject.Parse(payloadJson);


if (verify)
{
var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
var keyBytes = Encoding.UTF8.GetBytes(key);
var algorithm = (string)headerData["alg"];


var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign);
var decodedCrypto = Convert.ToBase64String(crypto);
var decodedSignature = Convert.ToBase64String(signature);


if (decodedCrypto != decodedSignature)
{
throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
}
}


return payloadData.ToString();
}


private static JwtHashAlgorithm GetHashAlgorithm(string algorithm)
{
switch (algorithm)
{
case "RS256": return JwtHashAlgorithm.RS256;
case "HS384": return JwtHashAlgorithm.HS384;
case "HS512": return JwtHashAlgorithm.HS512;
default: throw new InvalidOperationException("Algorithm not supported.");
}
}


// from JWT spec
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}


// from JWT spec
private static byte[] Base64UrlDecode(string input)
{
var output = input;
output = output.Replace('-', '+'); // 62nd char of encoding
output = output.Replace('_', '/'); // 63rd char of encoding
switch (output.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: output += "=="; break; // Two pad chars
case 3: output += "="; break; // One pad char
default: throw new System.Exception("Illegal base64url string!");
}
var converted = Convert.FromBase64String(output); // Standard base64 decoder
return converted;
}
}

然后是我的谷歌专用 JWT 类:

public class GoogleJsonWebToken
{
public static string Encode(string email, string certificateFilePath)
{
var utc0 = new DateTime(1970,1,1,0,0,0,0, DateTimeKind.Utc);
var issueTime = DateTime.Now;


var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds; // Expiration time is up to 1 hour, but lets play on safe side


var payload = new
{
iss = email,
scope = "https://www.googleapis.com/auth/gan.readonly",
aud = "https://accounts.google.com/o/oauth2/token",
exp = exp,
iat = iat
};


var certificate = new X509Certificate2(certificateFilePath, "notasecret");


var privateKey = certificate.Export(X509ContentType.Cert);


return JsonWebToken.Encode(payload, privateKey, JwtHashAlgorithm.RS256);
}
}

I've never used it but there is a JWT implementation on NuGet.

包裹: https://nuget.org/packages/JWT

资料来源: https://github.com/johnsheehan/jwt

.NET 4.0兼容: https://www.nuget.org/packages/jose-jwt/

你也可以点击这里: https://jwt.io/然后点击“库”。

这是我在.NET 中实现的(Google) JWT 验证。 它基于 Stack Overflow 和 GitHub 要点的其他实现。

using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;


namespace QuapiNet.Service
{
public class JwtTokenValidation
{
public async Task<Dictionary<string, X509Certificate2>> FetchGoogleCertificates()
{
using (var http = new HttpClient())
{
var response = await http.GetAsync("https://www.googleapis.com/oauth2/v1/certs");


var dictionary = await response.Content.ReadAsAsync<Dictionary<string, string>>();
return dictionary.ToDictionary(x => x.Key, x => new X509Certificate2(Encoding.UTF8.GetBytes(x.Value)));
}
}


private string CLIENT_ID = "xxx.apps.googleusercontent.com";


public async Task<ClaimsPrincipal> ValidateToken(string idToken)
{
var certificates = await this.FetchGoogleCertificates();


TokenValidationParameters tvp = new TokenValidationParameters()
{
ValidateActor = false, // check the profile ID


ValidateAudience = true, // check the client ID
ValidAudience = CLIENT_ID,


ValidateIssuer = true, // check token came from Google
ValidIssuers = new List<string> { "accounts.google.com", "https://accounts.google.com" },


ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)),
IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) =>
{
return certificates
.Where(x => x.Key.ToUpper() == kid.ToUpper())
.Select(x => new X509SecurityKey(x.Value));
},
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromHours(13)
};


JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
SecurityToken validatedToken;
ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);


return cp;
}
}
}

注意,为了使用它,您需要添加对 NuGet 包 System.Net.Http.Formatting.Extension的引用。否则,编译器将无法识别 ReadAsAsync<>方法。

最好使用标准和著名的库,而不是从头开始编写代码。

  1. JWT 用于编码和解码 JWT 令牌
  2. BouncyCastle 支持加密和解密,尤其是 RS256

使用这些库,您可以生成一个 JWT 令牌并使用 RS256对其进行签名,如下所示。

public string GenerateJWTToken(string rsaPrivateKey)
{
var rsaParams = GetRsaParameters(rsaPrivateKey);
var encoder = GetRS256JWTEncoder(rsaParams);


// create the payload according to the Google's doc
var payload = new Dictionary<string, object>
{
{ "iss", ""},
{ "sub", "" },
// and other key-values according to the doc
};


// add headers. 'alg' and 'typ' key-values are added automatically.
var header = new Dictionary<string, object>
{
{ "kid", "{your_private_key_id}" },
};


var token = encoder.Encode(header,payload, new byte[0]);


return token;
}


private static IJwtEncoder GetRS256JWTEncoder(RSAParameters rsaParams)
{
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaParams);


var algorithm = new RS256Algorithm(csp, csp);
var serializer = new JsonNetSerializer();
var urlEncoder = new JwtBase64UrlEncoder();
var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);


return encoder;
}


private static RSAParameters GetRsaParameters(string rsaPrivateKey)
{
var byteArray = Encoding.ASCII.GetBytes(rsaPrivateKey);
using (var ms = new MemoryStream(byteArray))
{
using (var sr = new StreamReader(ms))
{
// use Bouncy Castle to convert the private key to RSA parameters
var pemReader = new PemReader(sr);
var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
return DotNetUtilities.ToRSAParameters(keyPair.Private as RsaPrivateCrtKeyParameters);
}
}
}

Ps: RSA 私钥应该具有以下格式:

-----BEGIN RSA PRIVATE KEY-----
{base64 formatted value}
-----END RSA PRIVATE KEY-----

下面是另一个用于访问 G 套房用户和组的谷歌服务帐户的 只休息工作示例,通过 智威汤逊进行身份验证。这只有通过 Google 库的反射才能实现,因为 Google 对这些 API 的文档超出了 糟透了。任何习惯于使用微软技术编写代码的人都很难搞清楚谷歌服务中的一切是如何整合在一起的。

$iss = "<name>@<serviceaccount>.iam.gserviceaccount.com"; # The email address of the service account.
$sub = "impersonate.user@mydomain.com"; # The user to impersonate (required).
$scope = "https://www.googleapis.com/auth/admin.directory.user.readonly https://www.googleapis.com/auth/admin.directory.group.readonly";
$certPath = "D:\temp\mycertificate.p12";
$grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer";


# Auxiliary functions
function UrlSafeEncode([String] $Data) {
return $Data.Replace("=", [String]::Empty).Replace("+", "-").Replace("/", "_");
}


function UrlSafeBase64Encode ([String] $Data) {
return (UrlSafeEncode -Data ([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Data))));
}


function KeyFromCertificate([System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
$privateKeyBlob = $Certificate.PrivateKey.ExportCspBlob($true);
$key = New-Object System.Security.Cryptography.RSACryptoServiceProvider;
$key.ImportCspBlob($privateKeyBlob);
return $key;
}


function CreateSignature ([Byte[]] $Data, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
$sha256 = [System.Security.Cryptography.SHA256]::Create();
$key = (KeyFromCertificate $Certificate);
$assertionHash = $sha256.ComputeHash($Data);
$sig = [Convert]::ToBase64String($key.SignHash($assertionHash, "2.16.840.1.101.3.4.2.1"));
$sha256.Dispose();
return $sig;
}


function CreateAssertionFromPayload ([String] $Payload, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) {
$header = @"
{"alg":"RS256","typ":"JWT"}
"@;
$assertion = New-Object System.Text.StringBuilder;


$assertion.Append((UrlSafeBase64Encode $header)).Append(".").Append((UrlSafeBase64Encode $Payload)) | Out-Null;
$signature = (CreateSignature -Data ([System.Text.Encoding]::ASCII.GetBytes($assertion.ToString())) -Certificate $Certificate);
$assertion.Append(".").Append((UrlSafeEncode $signature)) | Out-Null;
return $assertion.ToString();
}


$baseDateTime = New-Object DateTime(1970, 1, 1, 0, 0, 0, [DateTimeKind]::Utc);
$timeInSeconds = [Math]::Truncate([DateTime]::UtcNow.Subtract($baseDateTime).TotalSeconds);


$jwtClaimSet = @"
{"scope":"$scope","email_verified":false,"iss":"$iss","sub":"$sub","aud":"https://oauth2.googleapis.com/token","exp":$($timeInSeconds + 3600),"iat":$timeInSeconds}
"@;




$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "notasecret", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable);
$jwt = CreateAssertionFromPayload -Payload $jwtClaimSet -Certificate $cert;




# Retrieve the authorization token.
$authRes = Invoke-WebRequest -Uri "https://oauth2.googleapis.com/token" -Method Post -ContentType "application/x-www-form-urlencoded" -UseBasicParsing -Body @"
assertion=$jwt&grant_type=$([Uri]::EscapeDataString($grantType))
"@;
$authInfo = ConvertFrom-Json -InputObject $authRes.Content;


$resUsers = Invoke-WebRequest -Uri "https://www.googleapis.com/admin/directory/v1/users?domain=<required_domain_name_dont_trust_google_documentation_on_this>" -Method Get -Headers @{
"Authorization" = "$($authInfo.token_type) $($authInfo.access_token)"
}


$users = ConvertFrom-Json -InputObject $resUsers.Content;


$users.users | ft primaryEmail, isAdmin, suspended;

下面是类和函数的列表:

open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.Logging
open Microsoft.AspNetCore.Authorization
open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.IdentityModel.Tokens
open System.IdentityModel.Tokens
open System.IdentityModel.Tokens.Jwt
open Microsoft.IdentityModel.JsonWebTokens
open System.Text
open Newtonsoft.Json
open System.Security.Claims
let theKey = "VerySecretKeyVerySecretKeyVerySecretKey"
let securityKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes(theKey))
let credentials = SigningCredentials(securityKey, SecurityAlgorithms.RsaSsaPssSha256)
let expires = DateTime.UtcNow.AddMinutes(123.0) |> Nullable
let token = JwtSecurityToken(
"lahoda-pro-issuer",
"lahoda-pro-audience",
claims = null,
expires =  expires,
signingCredentials = credentials
)


let tokenString = JwtSecurityTokenHandler().WriteToken(token)

使用 System.Security.Cryptography.RSA我已经修改了 John Sheehan 的 JWT 库代码,也就是 由@Levitikon 扩展使用 RS256RSASSA-PKCS1-v1_5 with the SHA-256 hash algorithm

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;


/// <summary>
/// adapted from<br/>
/// https://github.com/jwt-dotnet/jwt<br/>
/// https://stackoverflow.com/questions/10055158/is-there-any-json-web-token-jwt-example-in-c<br/>
/// https://stackoverflow.com/a/10106800/6620171<br/>
/// <br/>
/// JSON Web Token (JWT) is a compact, URL-safe means of representing<br/>
/// claims to be transferred between two parties.  The claims in a JWT<br/>
/// are encoded as a JSON object that is used as the payload of a JSON<br/>
/// Web Signature (JWS) structure or as the plaintext of a JSON Web<br/>
/// Encryption(JWE) structure, enabling the claims to be digitally<br/>
/// signed or integrity protected with a Message Authentication Code<br/>
/// (MAC) and/or encrypted.<br/>
/// <br/>
/// https://www.rfc-editor.org/rfc/rfc7519
/// </summary>
internal class JsonWebToken
{
/// <summary>
/// JWS uses cryptographic algorithms to digitally sign or create a MAC<br/>
/// of the contents of the JWS Protected Header and the JWS Payload.<br/>
/// <br/>
/// https://www.rfc-editor.org/rfc/rfc7518#section-3
/// </summary>
public enum JwsAlgorythm
{
/// <summary>
/// RSASSA-PKCS1-v1_5 using SHA-256<br/>
/// This section defines the use of the RSASSA-PKCS1-v1_5 digital<br/>
/// signature algorithm as defined in Section 8.2 of RFC 3447 [RFC3447]<br/>
/// (commonly known as PKCS #1), using SHA-2 [SHS] hash functions.<br/>
/// The RSASSA-PKCS1-v1_5 SHA-256 digital signature is generated as<br/>
/// follows: generate a digital signature of the JWS Signing Input using<br/>
/// RSASSA-PKCS1-v1_5-SIGN and the SHA-256 hash function with the desired<br/>
/// private key.  This is the JWS Signature value.<br/>
/// <br/>
/// https://www.rfc-editor.org/rfc/rfc7518#section-3.3<br/>
/// https://www.rfc-editor.org/rfc/rfc3447#section-8.2<br/>
/// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
/// </summary>
RS256,
/// <summary>
/// No digital signature or MAC performed<br/>
/// JWSs MAY also be created that do not provide integrity protection.<br/>
/// Such a JWS is called an Unsecured JWS.  An Unsecured JWS uses the<br/>
/// "alg" value "none" and is formatted identically to other JWSs, but<br/>
/// MUST use the empty octet sequence as its JWS Signature value.<br/>
/// Recipients MUST verify that the JWS Signature value is the empty<br/>
/// octet sequence.<br/>
/// <br/>
/// https://www.rfc-editor.org/rfc/rfc7518#section-3.6<br/>
/// https://www.rfc-editor.org/rfc/rfc7519#section-6
/// </summary>
none
}
public static string Encode(object payload, JwsAlgorythm algo, RSA rsa)
{
if (payload == null) { throw new ArgumentNullException("payload"); }
if (algo != JwsAlgorythm.RS256 && algo != JwsAlgorythm.none) { throw new ArgumentException("Invalid JwsAlgorythm specified"); }
if (rsa == null && algo == JwsAlgorythm.RS256) { throw new ArgumentNullException("Encoding of secured JWT requires an RSA object"); }
List<string> segments = new List<string>();
var header = new { typ = "JWT", alg = algo.ToString() };


string strHeader = JsonConvert.SerializeObject(header, Formatting.None);
string strPayload = JsonConvert.SerializeObject(payload, Formatting.None);
byte[] headerBytes = Encoding.UTF8.GetBytes(strHeader);
byte[] payloadBytes = Encoding.UTF8.GetBytes(strPayload);


segments.Add(Base64UrlEncode(headerBytes));
segments.Add(Base64UrlEncode(payloadBytes));


if (algo == JwsAlgorythm.none)
{
segments.Add(string.Empty);
return string.Join(".", segments.ToArray());
}


string stringToSign = string.Join(".", segments.ToArray());


byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
byte[] signature = rsa.SignData(bytesToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);


segments.Add(Base64UrlEncode(signature));
return string.Join(".", segments.ToArray());
}
public static Tuple<string, string> Decode(string token, JwsAlgorythm algo, bool verify, RSA rsa = null)
{
if (algo != JwsAlgorythm.RS256 && algo != JwsAlgorythm.none) { throw new ArgumentException("Invalid JwsAlgorythm specified"); }
if (verify && rsa == null && algo == JwsAlgorythm.RS256) { throw new ArgumentNullException("Verification of secured JWT requires an RSA object"); }
string[] parts = token.Split('.');
string header = parts[0];
string payload = parts[1];
byte[] crypto = Base64UrlDecode(parts[2]);


string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
JObject headerData = JObject.Parse(headerJson);
string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
JObject payloadData = JObject.Parse(payloadJson);


if (verify)
{
if (algo == JwsAlgorythm.none)
{
if (crypto.Length != 0)
{
throw new ApplicationException(string.Format("Invalid signature"));
}
}
else if (algo == JwsAlgorythm.RS256)
{
byte[] bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
bool valid = rsa.VerifyData(bytesToSign, crypto, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
if (!valid)
{
throw new ApplicationException(string.Format("Invalid signature"));
}
}
}
return new Tuple<string, string>(headerData.ToString(), payloadData.ToString());
}


// from JWT spec
private static string Base64UrlEncode(byte[] input)
{
string output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}


// from JWT spec
private static byte[] Base64UrlDecode(string input)
{
string output = input;
output = output.Replace('-', '+'); // 62nd char of encoding
output = output.Replace('_', '/'); // 63rd char of encoding
switch (output.Length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: output += "=="; break; // Two pad chars
case 3: output += "="; break; // One pad char
default: throw new Exception("Invalid base64url string");
}
byte[] converted = Convert.FromBase64String(output); // Standard base64 decoder
return converted;
}
}

用法:

X509Certificate2 cert = new X509Certificate2("C:\\test\\keypair.pfx", "notasecret");


long secsSinceEpoch = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var jwt = new
{
exp = secsSinceEpoch + 600,
iss = "my client id",
aud = "h++ps://webapi.com",
sub = "my subscriber id",
iat = secsSinceEpoch,
nbf = secsSinceEpoch,
jti = RandomString(21),
};


string jwtEncoded = JsonWebToken.Encode(jwt, JsonWebToken.JwsAlgorythm.RS256, cert.GetRSAPrivateKey());
Tuple<string, string> jwtDecoded = JsonWebToken.Decode(jwtEncoded, JsonWebToken.JwsAlgorythm.RS256, true, cert.GetRSAPublicKey());


Console.WriteLine(jwtDecoded);

产出:

({
"typ": "JWT",
"alg": "RS256"
}, {
"exp": 1668732075,
"iss": "my client id",
"aud": "h++ps://webapi.com",
"sub": "my subscriber id",
"iat": 1668731475,
"nbf": 1668731475,
"jti": "tCUpk2i5bNBVBcj7LzV5U"
})