简体   繁体   中英

node-oidc-provider 7.12.0 (JWT, authorization_code) invalid_token error on UserInfo endpoint (/me)

i have some issues getting the UserInfo endpoint working using JWT AccessTokens, it works fine with default settings when commenting out the resourceIndicators section.

I can get the access token using PostMan without issues, but when posting on the UserInfo (/me) endpoint the Bearer AccessToken, i got an invalid_token error.

here is my code:

const {Provider} = require('oidc-provider');

let hostname = process.env.HOSTNAME;
if (hostname === undefined) {
    hostname = "http://localhost"
}
const port = process.env.PORT || 3000;

if (port !== 80 && port !== 443) {
    hostname = hostname + ':' + port
}

const users = [
    {
        "id": "user1",
        "email": "user1@example.com",
        "authentication_method_reference": "mfa"
    }
]

const clients = [
    {
        "client_id": "client-1",
        "client_secret": "client-1-secret",
        "redirect_uris": [
            "http://localhost:3000"
        ]
    }
]

async function findAccount (ctx, id) {
    // This would ideally be just a check whether the account is still in your storage
    let account = users.find(user => {
        return user.id === id;
    })

    if (!account) {
        return undefined;
    }

    return {
        accountId: id,
        async claims() {
            return {
                sub: id,
                email: account.email,
                amr: [account.authentication_method_reference]
            };
        },
    };
}

const configuration = {
    clients: clients,
    conformIdTokenClaims: false,
    features: {
        devInteractions: {
            enabled: true
        },
        resourceIndicators: {
            defaultResource: (ctx, client, oneOf) => {
                return hostname;
            },
            enabled: true,
            getResourceServerInfo: (ctx, resourceIndicator, client) => {
                console.log('get resource server info', client);
                return ({
                    audience: resourceIndicator,
                    scope: 'openid',
                    accessTokenTTL: 2 * 60 * 60,
                    accessTokenFormat: 'jwt',
                });
            },
            useGrantedResource: (ctx, model) => { return true; }
        }
    },
    claims: {
        openid: [
            'sub',
            'email',
            'amr'
        ]
    },
    cookies: {
        keys: 'super,secret'.split(',')
    },
    pkce: {
        required: () => false
    },
    // Used to skip the 'approval' page
    async loadExistingGrant(ctx) {
        const grantId = (ctx.oidc.result
            && ctx.oidc.result.consent
            && ctx.oidc.result.consent.grantId) || ctx.oidc.session.grantIdFor(ctx.oidc.client.clientId);

        if (grantId) {
            // keep grant expiry aligned with session expiry
            // to prevent consent prompt being requested when grant expires
            const grant = await ctx.oidc.provider.Grant.find(grantId);

            // this aligns the Grant ttl with that of the current session
            // if the same Grant is used for multiple sessions, or is set
            // to never expire, you probably do not want this in your code
            if (ctx.oidc.account && grant.exp < ctx.oidc.session.exp) {
                grant.exp = ctx.oidc.session.exp;

                await grant.save();
            }

            return grant;
        } else {
            const grant = new ctx.oidc.provider.Grant({
                clientId: ctx.oidc.client.clientId,
                accountId: ctx.oidc.session.accountId,
            });

            grant.addOIDCScope('openid');
            grant.addResourceScope(hostname, 'openid');

            await grant.save();

            return grant;
        }
    },
    extraTokenClaims: async (ctx, token) => {
        return findAccount(ctx, token.accountId).then(account => {
            return account.claims()
        })
    },
    findAccount: findAccount
};

const oidc = new Provider(hostname, configuration);

function handleServerError(ctx, err) {
    console.log(err);
}

function handleGrantErrors({headers: {authorization}, oidc: {body, client}}, err) {
    console.log(err);
}

function handleAccessToken(token) {
    console.log(token);
}

oidc.on('grant.error', handleGrantErrors);
oidc.on('introspection.error', handleGrantErrors);
oidc.on('revocation.error', handleGrantErrors);
oidc.on('server_error', handleServerError);
oidc.on('access_token.issued', handleAccessToken);

oidc.listen(port, () => {
    console.log(`oidc-provider listening on port ${port}.`)
})

I tried different configurations without success, the generated JWT AccessToken looks fine to me (see bellow), but i'm unable to query the UserInfo endpoint with it.

{
  "sub": "user1",
  "email": "user1@example.com",
  "amr": [
    "mfa"
  ],
  "jti": "-7gURc8Y1SXqOXhWR691i",
  "iat": 1668777371,
  "exp": 1668784571,
  "scope": "openid",
  "client_id": "client-1",
  "iss": "http://localhost:3000",
  "aud": "http://localhost:3000"
}

Thanks in advance.

As per the module documentation's userinfo feature.

Its use requires an opaque Access Token with at least openid scope that's without a Resource Server audience.

In essence, this implementation's userinfo endpoint will not work with JWT Access Tokens that are issued for a particular resource server. This is because the userinfo endpoint is a resource for the client and if it was callable with an access token that was sent to a resource server, that resource server can turn around and query userinfo which is not the intended use of the userinfo endpoint.

In cases when JWT Access Tokens are issued the client will get all scope requested userinfo claims in the ID Token it receives, removing the need to call userinfo.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM