Error in GetSession() - ApplyPreAuthenticateFilters

Using my client app with SS with my current settings, I don’t see any issues.
I use the IdentityJwtAuthProvider, the AuthSecret and my custom Auth Provider derived from IdentityCredentialsAuthProvider.

Tokens are generated and signature validated correctly. I access all my api and the built-in ones like /ui and /admin-ui ok.

I think it happened when I accessed the admin-ui with the AuthSecret but now I can’t reproduce… I am the only one using the app.
I can see this error in log a dozen of times at the same second.

Error in GetSession() when ApplyPreAuthenticateFilters
System.ArgumentException: IDX12723: Unable to decode the payload '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' as Base64Url encoded string.
 ---> System.Text.Json.JsonException: IDX11022: Expecting json reader to be positioned on 'JsonTokenType.String', reader was positioned at: 'Number', Reading: 'System.IdentityModel.Tokens.Jwt.JwtPayload.jti', Position: '544', CurrentDepth: '1', BytesConsumed: '546'.
   at Microsoft.IdentityModel.Tokens.Json.JsonSerializerPrimitives.ReadString(Utf8JsonReader& reader, String propertyName, String className, Boolean read)
   at System.IdentityModel.Tokens.Jwt.JwtPayload.CreatePayload(Byte[] bytes, Int32 length)
   at Microsoft.IdentityModel.Tokens.Base64UrlEncoding.Decode[T](String input, Int32 offset, Int32 length, Func`3 action)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityToken.DecodeJws(String payload)
   --- End of inner exception stack trace ---
   at System.IdentityModel.Tokens.Jwt.JwtSecurityToken.DecodeJws(String payload)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityToken.Decode(String[] tokenParts, String rawData)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ReadJwtToken(String token)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(String token, TokenValidationParameters validationParameters, BaseConfiguration currentConfiguration, SecurityToken& signatureValidatedToken, ExceptionDispatchInfo& exceptionThrown)
--- End of stack trace from previous location ---
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, JwtSecurityToken outerToken, TokenValidationParameters validationParameters, SecurityToken& signatureValidatedToken)
   at ServiceStack.Auth.IdentityJwtAuthProvider`3.PreAuthenticateAsync(IRequest req, IResponse res) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Extensions/Auth/IdentityJwtAuthProvider.cs:line 321
   at ServiceStack.ServiceStackHost.ApplyPreAuthenticateFiltersAsync(IRequest httpReq, IResponse httpRes)
   at ServiceStack.ServiceExtensions.GetSessionInternalAsync(IRequest httpReq, Boolean reload, Boolean async, CancellationToken token) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/ServiceExtensions.cs:line 254

Do I miss something?

services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>();

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["JwtBearer:ValidIssuer"],
            ValidAudience = builder.Configuration["JwtBearer:ValidAudience"] 
        };
    });
public class JwtBearerPostConfigureOptions(ILogger<JwtBearerPostConfigureOptions> logger)
    : IPostConfigureOptions<JwtBearerOptions>
{

    public void PostConfigure(string? name, JwtBearerOptions options)
    {
        options.TokenValidationParameters.IssuerSigningKey =new RsaSecurityKey(SecurityContext.GetCertificate()?.GetRSAPrivateKey());
        logger.LogDebug("IssuerSigningKey is set to RsaSecurityKey from SecurityContext certificate private key.");
    }
}

 var auth = new AuthFeature(IdentityAuth.For<ApplicationUser>(options =>
                {
                    options.SessionFactory = () => new PEUserSession();
                    options.JwtAuth(x =>
                    {
                        x.IncludeConvertSessionToTokenService = true;
                        x.RequireSecureConnection = true;
                        x.HashAlgorithm = SecurityAlgorithms.RsaSha256;;
                        x.ExpireTokensIn = TimeSpan.FromDays(365 * 25);
                        x.RestoreSessionFromState = false;
                        x.PersistSession = true;
                        x.OnSessionCreated = (session, claims, req) =>
                        {
                           //...
                        };
                        x.OnTokenCreated = (req, user, claims) =>
                        {//...
                    });
                }));

Is there a way to prevent this other than filter out these entries from log?

Sounds like it’s not able to decode an invalid JWT, what’s the JWT it’s trying to decode?

I see this in the log. Looks like I have used my custom auth to login but I did not get the admin role. I then use the AuthSecret to login. I then end up having my ss-tok (which is valid and signature validated ok) and the authSecret… don’t know if it messes things.

2026-02-24 08:58:47.5761|TRACE|Session:|Class:PE.AuthenticationRequestFilter|Method:ProcessAuthenticationRequest|Line:0|FilePath:|GlobalRequestFiltersAsync - Request {
	Resolver: PE.AppHost,
	HttpContext: Microsoft.AspNetCore.Http.DefaultHttpContext,
	HttpRequest: Microsoft.AspNetCore.Http.DefaultHttpRequest,
	OriginalRequest: Microsoft.AspNetCore.Http.DefaultHttpRequest,
	Response: ServiceStack.Host.NetCore.NetCoreResponse,
	Verb: GET,
	RequestAttributes: 67108940,
	RequestPreferences: 
	{
		AcceptEncoding: "gzip, deflate, br, zstd",
		AcceptsBrotli: True,
		AcceptsDeflate: True,
		AcceptsGzip: True
	},
	Dto: {},
	ContentType: application/json,
	User: 
	{
		Claims: [],
		Identities: 
		[
			{
				isAuthenticated: False,
				claims: [],
				nameClaimType: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
				roleClaimType: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
			}
		],
		Identity: 
		{
			IsAuthenticated: False,
			Claims: [],
			NameClaimType: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
			RoleClaimType: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
		}
	},
	TraceId: 40000189-0802-2e00-b63f-84710c7967bb,
	IsLocal: False,
	UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
	ResponseContentType: application/json,
	HasExplicitResponseContentType: True,
	Cookies: 
	{
		lang: 
		{
			comment: "",
			httpOnly: False,
			discard: False,
			domain: "",
			expired: False,
			expires: 0001-01-01,
			name: lang,
			path: "",
			port: "",
			secure: False,
			timeStamp: 2026-02-24T08:58:47.5778111-05:00,
			value: en-US,
			version: 0
		}
	},
	Items: 
	{
		__timestamp: 29327648175108,
		__haspreauth: True
	},
	Headers: 
	[
		{
			key: Accept,
			value: */*
		},
		{
			key: Accept-Encoding,
			value: "gzip, deflate, br, zstd"
		},
		{
			key: Accept-Language,
			value: "en-US,en;q=0.9,mo;q=0.8,en-GB;q=0.7,el;q=0.6,ja;q=0.5,es-AR;q=0.4,es;q=0.3,ru;q=0.2,hu;q=0.1,fr-CA;q=0.1,fr;q=0.1"
		},
		{
			key: Authorization,
			value: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjV0ZCJ9....
		},
		{
			key: Connection,
			value: close
		},
		{
			key: Content-Type,
			value: application/json
		},
		{
			key: Cookie,
			value: lang=en-US
		},
		{
			key: Host,
			value: "..."
		},
		{
			key: Referer,
			value: "https://.../admin-ui/logging?edit=73"
		},
		{
			key: User-Agent,
			value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
		},
		{
			key: sec-ch-ua-platform,
			value: """Windows"""
		},
		{
			key: sec-ch-ua,
			value: """Not:A-Brand"";v=""99"", ""Google Chrome"";v=""145"", ""Chromium"";v=""145"""
		},
		{
			key: authsecret,
			value: <hidden>
		},
		{
			key: sec-ch-ua-mobile,
			value: ?0
		},
		{
			key: sec-fetch-site,
			value: same-origin
		},
		{
			key: sec-fetch-mode,
			value: cors
		},
		{
			key: sec-fetch-dest,
			value: empty
		},
		{
			key: priority,
			value: "u=1, i"
		}
	],
	QueryString: 
	[
		{
			key: Id,
			value: 73
		}
	],
	FormData: [],
	RawUrl: /api/AdminQueryRequestLogs?Id=73,
	AbsoluteUri: "https://.../api/AdminQueryRequestLogs?Id=73",
	UserHostAddress: ...,
	Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjV0ZCJ9....,
	IsSecureConnection: True,
	AcceptTypes: 
	[
		*/*
	],
	PathInfo: /api/AdminQueryRequestLogs,
	OriginalPathInfo: /api/AdminQueryRequestLogs,
	UseBufferedStream: True,
	ContentLength: 0,
	Files: [],
	UrlReferrer: "https://.../admin-ui/logging?edit=73",
	HttpMethod: GET,
	Accept: */*,
	RemoteIp: ...,
	IsDirectory: False,
	IsFile: False,
	Id: 00-012a0fae86b48dee9f1361bf88282f47-6a7556de67fd3b1b-00
}

2026-02-24 08:58:47.5761|TRACE|Session:|Class:PE.AuthenticationRequestFilter|Method:ProcessAuthenticationRequest|Line:0|FilePath:|GlobalRequestFiltersAsync - Session {
	__type: "PE.Model.PEUserSession, PE.Model",
	isGuestOnly: False,
	isMobile: False,
	ttl: 0,
	idleTimeOut: 0,
	ppaUserId: 0,
	kIndex: 0,
	id: d95f8770-fc3c-4367-9852-057befebfa5e,
	createdAt: 2026-02-24T13:58:47.5778286Z,
	lastModified: 2026-02-24T13:58:47.5778286Z,
	isAuthenticated: False,
	fromToken: False,
	tag: 0,
	providerOAuthAccess: [],
	meta: {},
	user: 
	{
		claims: [],
		identities: []
	}
}

2026-02-24 08:58:47.5761|TRACE|Session:|Class:PE.AuthenticationRequestFilter|Method:ProcessAuthenticationRequest|Line:0|FilePath:|GlobalRequestFiltersAsync - Request DTO {
	
} ot type "ServiceStack.Jobs.AdminQueryRequestLogs, ServiceStack.Jobs"

That JWT is valid and can be decoded, but this looks like it was generated from ServiceStack’s JWT Auth Provider:

{
  "iss": "ssjwt",
  "sub": 1,
  "iat": 1771937823,
  "exp": 4925537823,
  "name": "brabant.j",
  "preferred_username": "brabant.j",
  "roles": [
    "lineOperator",
    ...
    "guest"
  ],
  "perms": [
    "productionExecution",
    "settingsGeneral",
    ...
    "peReports",
    "viewPrdExecution"
  ],
  "jti": 15,
  "domain": "int",
  ...
}

Where as your code uses ASP .NET’s Identity JWT which isn’t compatible. Is this a JWT for a different application that uses ServiceStack JWT?

No, I am using the IdentityJwtAuthProvider but I left many things the old way when creating / restoring the token (OnTokenCreated and OnSessionCreated left out from code example) from previous implementation.

I’m not going to know what the issue is with your custom JWT hybrid setup. But from the StackTrace it says it couldn’t parse the JSON where it’s not able to parse the jti integer value because it was expecting a string.

If this is something your custom configuration is doing, I’d suggest removing it or changing the type to a string.

Fair enough, but are you saying that if we see the jti as a number it means I would have some left over from the ServiceStack JWT ? I am not doing anything in my codebase with that “jti” at all. Where that comes from? I don’t even know how I would change it’s datatype…

I am pretty sure there is no trace of ServiceStack JWT in my solution. But I understand it might not be a ServiceStack issue as well…so.. thanks anyway.

Any hints will be appreciated.

A unique jti does get added by ServiceStack as a string claim which I’ve just added a JWT Validation test to verify that the jti id is serialized and deserialized as a string.

So it’s not clear how jti is being serialized as an int here, ServiceStack JWT does serialize it as an int, but Identity JWT serializes it as a string and expects to deserialize it as a string.

Anyway if you change the JwtId to return a non integer even JsonObject will serialize it as a string:

options.JwtAuth(x => {
    x.ResolveJwtId = req => "jti-" + x.NextJwtId();
});

Or you could set it to x.ResolveJwtId = null where it wont include it. But I’d look into which code path is serializing it as a string, as it should only be using Identity JWT to encode and decode it.

Thank you very much!