Showing posts with label JWT. Show all posts
Showing posts with label JWT. Show all posts

Thursday, May 28, 2026

A lesson learned about JWT tokens "Issued At" attribute

One of our systems integrates with Apple Pay and a JWT token is used to authenticate server-to-server requests from us to their backends. Someone noticed that a small amount of requests fail. It was usually less than 5% of failed requests, raising to 30% occasionally for short period of times.

The code was audited and we've found that someone just wrote:

private string GetToken()
{
	var now    = DateTime.UtcNow;
	var expiry = now.AddSeconds(this.Settings.MaxTokenAge);

	ECDsaSecurityKey eCDsaSecurityKey = GetEcdsaSecuritKey();

	var handler = new JsonWebTokenHandler();
	string jwt = handler.CreateToken(new SecurityTokenDescriptor
	{
		Issuer   = this.Settings.IssuerId,
		Audience = this.Settings.AppstoreAudience,
        
		NotBefore = now,
		Expires   = expiry,
		IssuedAt  = now,
		Claims    = new Dictionary<string, object>
		{
        	...
		},

		SigningCredentials = ...
	});

	return jwt;
}

Looks great.

Problem is, it does not always work.

What we've found out is that when there's a subtle, small difference of current time between your servers and their servers, our DateTime.UtcNow can be their future. And they (correctly) reject tokens from the future.

What was applied there? Well, just:

private string GetToken()
{
	var now    = DateTime.UtcNow.AddMinutes(-1);
	var expiry = now.AddSeconds(this.Settings.MaxTokenAge);

The result? 0% of failed requests.

Lesson learned.

Thursday, October 5, 2017

JWT tokens between .NET and node.js

Node.js JWT tokens are straightforward
var jwt = require('jsonwebtoken');

var secret = "This is my shared, not so secret, secret!";
var payload = {
    "unique_name":"myemail@myprovider.com",
    "role":"Administrator",
    "nbf":1499944099,
    "exp":1599947699,
    "iat":1499944099,
    "iss":"http://my.tokenissuer.com",
    "aud":"http://my.website.com"};

var token = jwt.sign(payload, secret);

console.log( token );

// -------------------------

var decoded = jwt.verify(token, secret);
console.log( decoded.unique_name );
thanks to the jsonwebtoken package.

In case of .NET, things are a little bit more complicated as the JWT is implemented in System.IdentityModel.Tokens.Jwt. This is unfortunate, as the 5.x.x version of this library has been reimplemented to rely on Microsoft.IdentityModel. Because of this, many older tutorials became deprecated and people tend to have different issues.

To have a working code, reference System.IdentityModel.Tokens.Jwt 5.1.4 or newer from Nuget and then

using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var plainTextSecurityKey = "This is my shared, not so secret, secret!";

            var signingKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(plainTextSecurityKey));

            var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(signingKey,
                Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256Signature);

            // -------------------------

            var claimsIdentity = new ClaimsIdentity(new List<Claim>()
            {
                new Claim(ClaimTypes.Name, "myemail@myprovider.com"),
                new Claim(ClaimTypes.Role, "Administrator"),
            }, "Custom");

            var securityTokenDescriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor()
            {
                Audience = "http://my.website.com",
                Issuer   = "http://my.tokenissuer.com",

                Subject            = claimsIdentity,                
                SigningCredentials = signingCredentials                              
            };

            var tokenHandler          = new JwtSecurityTokenHandler();
            var plainToken            = tokenHandler.CreateToken(securityTokenDescriptor);
            var signedAndEncodedToken = tokenHandler.WriteToken(plainToken);

            Console.WriteLine(plainToken);
            Console.WriteLine(signedAndEncodedToken);

            // -------------------------

            var tokenValidationParameters = new TokenValidationParameters()
            {
                ValidAudiences = new string[]
                {
                    "http://my.website.com",
                    "http://my.otherwebsite.com"
                },
                ValidIssuers = new string[]
                {
                    "http://my.tokenissuer.com",
                    "http://my.othertokenissuer.com"
                },
                IssuerSigningKey = signingKey                
            };

            Microsoft.IdentityModel.Tokens.SecurityToken validatedToken;
            var validatedPrincipal = tokenHandler.ValidateToken(signedAndEncodedToken,
                tokenValidationParameters, out validatedToken);

            Console.WriteLine(validatedPrincipal.Identity.Name);
            Console.WriteLine(validatedToken.ToString());

            Console.ReadLine();
        }
    }
}