I try to allow JWT introspection on my oidc provider, but it always results to an { "active": false }. I precise that i don't encounter problem for opaque introspection and I turned on the feature jwtIntrospection on the oidc configuration.
JWT payload example that return active false :
{
"realm": "aq",
"last_password_update": null,
"jti": "pVMmyVTjmzVqLsl-9p5Ms",
"sub": "953b49e3-14ee-4278-b818-1666fb0c1f67",
"iat": 1707926210,
"exp": 1707928010,
"client_id": "953b49e3-14ee-4278-b818-1666fb0c1f67",
"iss": "http://localhost:3080/op",
"aud": "http://localhost:3080/api/v1/"
}
Introspect response when it's an opaque token :
{
"active": true,
"realm": "aq",
"invitation_token": null,
"last_password_update": null,
"client_id": "953b49e3-14ee-4278-b818-1666fb0c1f67",
"exp": 1707927912,
"iat": 1707926112,
"iss": "http://localhost:3080/op",
"token_type": "Bearer"
}
Am I missing something ? Thanks!
EDIT : oidc configuration and token introspection
private static oidcConfiguration = async (): Promise<Configuration> => ({
acrValues: getAcrPolicies().map((acrPolicy) => acrPolicy.acrValue),
conformIdTokenClaims: false,
extraClientMetadata: {
properties: [
'accessTokenLifetime',
'cancel_logout_redirect_uris',
'consumedResources',
'isIntrospectionAllowed',
'managedResource',
'pkceRequired',
'refreshTokenTotalLifetime',
'serviceId',
],
},
extraParams: ['sign_in_only', 'sign_up', 'invitation_token', 'sub', 'login_hint'],
extraTokenClaims,
scopes: getScopePolicies().map((scope) => scope.scope),
clients: getClients(),
cookies: {
names: {
interaction: 'op_interaction',
resume: 'op_interaction_resume',
session: 'op_session',
state: 'op_state',
},
long: {
httpOnly: true,
overwrite: true,
signed: true,
secure: process.env.APP_ENV !== 'local',
sameSite: 'none',
path: process.env.OP_PATH,
},
short: {
httpOnly: true,
overwrite: true,
signed: true,
secure: process.env.APP_ENV !== 'local',
sameSite: 'none',
path: process.env.OP_PATH,
},
keys: getMainConfiguration().cookieKeys,
},
interactions: {
policy: await OidcService.getPolicy(),
url(_, interaction: Interaction) {
return concatUrl('/op', '/interaction', interaction.uid);
},
},
routes: {
authorization: '/auth',
code_verification: '/device',
device_authorization: '/device/auth',
end_session: '/session/end',
introspection: '/token/introspection',
jwks: '/jwks',
pushed_authorization_request: '/request',
registration: '/reg',
revocation: '/token/revocation',
token: '/token',
userinfo: '/me',
},
features: {
// Node OIDC Provider parameters
backchannelLogout: { enabled: true },
claimsParameter: { enabled: false },
clientCredentials: { enabled: true },
dPoP: { enabled: false },
devInteractions: { enabled: false },
deviceFlow: { enabled: false },
encryption: { enabled: false },
introspection: {
enabled: true,
allowedPolicy: (ctx, client) => {
const clientMetadata = client.metadata();
return clientMetadata.isIntrospectionAllowed;
},
},
jwtIntrospection: { enabled: true, ack: 'draft-10' },
jwtResponseModes: { enabled: false },
jwtUserinfo: { enabled: false },
mTLS: { enabled: false },
pushedAuthorizationRequests: { enabled: false },
registration: { enabled: false },
registrationManagement: { enabled: false },
requestObjects: {},
resourceIndicators: {
// enable = true, to activate resourceIndicator
enabled: true,
// defaultResource = undefined, to return an access token without audience when no resource is explicitly request during AT request
defaultResource: (ctx) => undefined,
getResourceServerInfo: async (ctx, resourceIndicator, client) => {
return OidcService.getResourceServerInfo(ctx, resourceIndicator, client);
},
useGrantedResource: () => false,
},
revocation: { enabled: true },
rpInitiatedLogout: {
enabled: true,
logoutSource: async (ctx: KoaContextWithOIDC, form: string): Promise<void> => {
ctx.body = await logoutPage(ctx, form);
},
postLogoutSuccessSource: async (ctx: KoaContextWithOIDC): Promise<void> => {
ctx.body = await postLogoutSuccessPage(ctx);
},
},
userinfo: { enabled: true },
webMessageResponseMode: { enabled: false },
},
findAccount: (ctx: KoaContextWithOIDC, sub: string): any => AccountService.findAccount(ctx, sub),
ttl: ttlConfiguration,
pkce: {
methods: ['S256'],
required: (ctx, client) => {
return client.pkceRequired !== false;
},
},
renderError: (ctx, out, error) => {
const err = error as errorsType.OIDCProviderError;
const clientId = ctx.oidc?.client?.clientId;
return RenderPageUtils.renderError(ctx.req as Request, ctx.res as Response, {
statusCode: err?.statusCode ? err.statusCode.toString() : '500',
reason: out.error_description,
clientId,
});
},
});
and my token introspection (performed at an express route middleware) :
const client = await getClientAPI();
const instrospectResponse = await client.introspect(accessToken, 'access_token');
client value :
{
authorization_signed_response_alg: 'RS256',
client_id: 'client-api',
grant_types: [
'authorization_code'
],
id_token_signed_response_alg: 'ES256',
introspection_endpoint_auth_method: 'private_key_jwt',
introspection_endpoint_auth_signing_alg: 'ES256',
introspection_signed_response_alg: 'ES256',
response_types: [
'code'
],
revocation_endpoint_auth_method: 'private_key_jwt',
revocation_endpoint_auth_signing_alg: 'ES256',
token_endpoint_auth_method: 'private_key_jwt',
token_endpoint_auth_signing_alg: 'ES256'
}