0

The following is my configuration for the oidc provider. I add resource indicators under the features. But it does not working at all. How can it issues JWT access token using node-oidc-provider in authorization_code workflow?

The provider version is 7.11.4.

clients: [
    {
      client_id: '0oa5pa23mzKPWYtvV5d7',
      redirect_uris: ['https://jwt.io', 'http://localhost:3000/login/callback'], // using jwt.io as redirect_uri to show the ID Token contents
      response_types: ['code'],
      application_type: 'web',
      token_endpoint_auth_method: 'none',
      scope: 'openid offline_access profile email',
      grant_types: ['authorization_code', 'refresh_token'],
    },
  ],
  routes: {
    authorization: '/oauth2/v1/authorize',
    token: '/oauth2/v1/token',
    revocation: '/oauth2/v1/revoke',
    userinfo: '/oauth2/v1/userinfo',
  },
  clientBasedCORS(ctx, origin, client) {
    if (client.clientId === '0oa5pa23mzKPWYtvV5d7' && origin === 'http://localhost:3000') {
      return true;
    }


    return false;
  },
  pkce: {
    required: () => true,
  },
  interactions: {
    url(ctx, interaction) { // eslint-disable-line no-unused-vars
      return `/interaction/${interaction.uid}`;
    },
  },
  cookies: {
    keys: ['some secret key', 'and also the old rotated away some time ago', 'and one more'],
  },
  claims: {
    address: ['address'],
    email: ['email', 'email_verified'],
    phone: ['phone_number', 'phone_number_verified'],
    profile: ['birthdate', 'family_name', 'gender', 'given_name', 'locale', 'middle_name', 'name',
      'nickname', 'picture', 'preferred_username', 'profile', 'updated_at', 'website', 'zoneinfo'],
  },
  features: {
    resourceIndicators: {
      getResourceServerInfo: () => ({
        audience: 'solid',
        accessTokenTTL: 2 * 60 * 60, // 2 hours
        accessTokenFormat: 'jwt',
        jwt: {
          sign: { alg: 'ES256' },
        },
      }),
    },
  },

3 Answers3

0

defaultResource also needs to be updated. In case of jwt, this function has to return proper value. By default, it returns undefined.

        resourceIndicators: {
            defaultResource: (ctx, client, oneOf) => {
                if (oneOf) return oneOf;
                return client['access_token_type'] === 'opaque' ? undefined : `https://${clientId}.com`;
            },
            getResourceServerInfo: (ctx, resourceIndicator, client) => {
                return ({
                    scope: client.scope,
                    accessTokenTTL: 2 * 60 * 60,
                    accessTokenFormat: 'jwt',
                });
            }
        },
karepu
  • 198
  • 1
  • 6
0

add also useGrantedResource to the resourceIndicators

  useGrantedResource: (ctx, model) => {
    // @param ctx - koa request context
    // @param model - depending on the request's grant_type this can be either an AuthorizationCode, BackchannelAuthenticationRequest, RefreshToken, or DeviceCode model instance.
    return true;
  }
0

To issue accessToken for usage on protected resources you can:

  1. Ensure you have your api: api1.example.com, api2.example.com, api3.example.com
  2. Ensure you have defined extraClientMetadata

/** @See https://github.com/panva/node-oidc-provider/tree/main/docs#extraClientMetadata */

/** Allows for custom client metadata to be defined, validated, manipulated as well as for existing property validations to be extended. Existing properties are snakeCased on a Client instance (e.g. client.redirectUris), new properties (defined by this configuration) will be avaialable with their names verbatim (e.g. client['urn:example:client:my-property']) */
module.exports = {

    /**
     * isInternalClient: true | false, wether the client is for first party or for third party
     * 
     * resourcesScopes: ressource scope clients is allowed to requested for
     * 
     * allowedResources: ressource server client is allowed to request token for
     */
    properties: ['allowedResources', 'resourcesScopes', "isInternalClient"],


    validator: function extraClientMetadataValidator(ctx, key, value, metadata) {},
};
  1. You can register your apis on the idP


// src/oidc/config/resources-servers.js
// Define list of resources server
let resourcesList = {
};

// Register api 1 on the IdProvider
// Assume process.env.API1_BASE_URL = https://api1.example.com
resourcesList[process.env.API1_BASE_URL] = {

    // These scope is accept the by the resource server to make authorization decisions
    scope: 'offline_access api:query api:get api:post api:patch api:delete',

    // Audience
    audience: process.env.API1_BASE_URL,

    // accessTokenFormat?: 'opaque' | 'jwt' | 'paseto'
    // Please see https://github.com/panva/node-oidc-provider/tree/main/docs#getresourceserverinfo sections Resource Server (API) for more customization
    accessTokenFormat: 'opaque',
};

// Register api 2 on the IdProvider
// Assume process.env.API2_BASE_URL = https://api2.example.com
resourcesList[process.env.API2_BASE_URL] = {
    scope: 'offline_access api:query api:get api:post api:patch api:delete',
    audience: process.env.API2_BASE_URL,
    accessTokenFormat: 'opaque',
};

// Register api 3 on the IdProvider
// Assume process.env.API3_BASE_URL = https://api3.example.com
resourcesList[process.env.API3_BASE_URL] = {
    scope: 'offline_access api:query api:get api:post api:patch api:delete',
    audience: process.env.API3_BASE_URL,
    accessTokenFormat: 'opaque',
};

  1. Now configure each client with it allowed resources server it can request accessToken for and related resource scope:
// src/oidc/config/clients.js
{
  client_id: "client1",
  // ...
  // Other client meta data
  // ...
  // These below are clientExtraMetadata. Mandatory for api access_token usage
  allowedResources: [process.env.API1_BASE_URL, process.env.API2_BASE_URL],
  resourcesScopes: "offline_access api:query api:get api:post api:patch api:delete",
  isInternalClient: true
},

{
  client_id: "client2",
  // ...
  // Other clie
  // ...
  // These below are clientExtraMetadata. Mandatory for api access_token usage
  allowedResources: [process.env.API1_BASE_URL,  process.env.API3_BASE_URL],
  resourcesScopes: "offline_access api:query api:get api:post api:patch api:delete",
  isInternalClient: true
}
  1. Define your config.features.resourceIndicator properly like according to previous stuffs

 /** @See https://github.com/panva/node-oidc-provider/tree/main/docs#featuresresourceindicators */
 resourceIndicators: {
    enabled: true,

    defaultResource(ctx, client, oneOf) {
        return Array.isArray(ctx.oidc.params?.resource)
            ? ctx.oidc.params?.resource[0]
            : ctx.oidc.params?.resource;
    },
    useGrantedResource: async function useGrantedResource(ctx, model) { 
        return true;
    },
    getResourceServerInfo(ctx, resourceIndicator, client) {


        // Ensure resourceIndicator is provided and this resource exist
        if (!resourceIndicator || !resourcesList[resourceIndicator]) {
            throw new errors.InvalidRequest("invalid_request", "Invalid resource server");
        }

        // Get this resource infos
        var targetResourceServer = resourcesList[resourceIndicator];

        // Client request access_token for api must defined these 2 metadata: allowResources, ressourcesScopes
        if (!Array.isArray(client.allowedResources) || !(client.allowedResources.includes(resourceIndicator))) {
            throw new errors.InvalidClientMetadata("invalid_client_metadata", "allowedResources & allowedResources are mandatory or you cannot request access token for this server");
        }

        // Now ensure client get access_token for scope it not defined
        let clientAllowedScope = "";
        if (client.resourcesScopes) {
            var scopesList = client.resourcesScopes.split(' ');
            clientAllowedScope = scopesList.filter((scopeItem) => {
                return targetResourceServer.scope.includes(scopeItem);
            });
        } else {
            throw new errors.InvalidClientMetadata("invalid_client_metadata", "Please specify at least one scope");
        }

        console.log(">>----Client ressource allowed:", client.allowedResources)
        console.log(">>----Client ressource scopes:", client.resourcesScopes)
        console.log(">>----Target ressource server is:", targetResourceServer)
        console.log(">>----resourceIndicator is :", resourceIndicator)
        console.log(">>----Client Scope allowed:", clientAllowedScope.join(' '))


        // Update the acces_token ressource to issued
        targetResourceServer.scope = clientAllowedScope.join(' ');
        return targetResourceServer;
    },
}
  1. Now you can issue accessToken targeting your apis even as jwt, passeto or opaque
Dahkenangnon
  • 66
  • 1
  • 9