NodeJS OIDC Provider getting aud and resource server errors upgrade from 6.x to 7.x - node.js

I am trying to upgrade [node-oidc-provider]https://github.com/panva/node-oidc-provider from version 6.x to version 7.x. I am using the authorization code flow for a React application.
I am getting an error regarding aud(audience) being a required field for JWT tokens:
Error: JWT Access Tokens must contain an audience, for Access Tokens without audience (only usable at the userinfo_endpoint) use an opaque format
Looking at the code and documentation, I tried to update the aud field by defining a function in formats.jwt.customizers.
I am not sure if this is the right solution as after doing that, I faced an issue regarding invalid resource server configuration:
Error: invalid Resource Server jwt configuration
Below is my existing configuration (provided file is support/configuration.js):
module.exports = {
clients: [
{
"application_type": "web",
"grant_types": [
"authorization_code"
],
"id_token_signed_response_alg": "RS256",
"post_logout_redirect_uris": [
"http://localhost:3001"
],
"require_auth_time": false,
"response_types": [
"code"
],
"subject_type": "public",
"token_endpoint_auth_method": "none",
"introspection_endpoint_auth_method": "none",
"revocation_endpoint_auth_method": "none",
"request_uris": [],
"client_id_issued_at": 1622600472.0,
"client_id": "my_client_id",
"client_name": "Sample client application",
"client_secret_expires_at": 0.0,
"client_secret": "my_client_secret" ,
"redirect_uris": [
"http://localhost:3001/callback"
],
"client_background_uri": "/public/img/default.png",
"app_id": "sample_app"
}
],
clientBasedCORS: (ctx, origin, client)=>{
return 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'],
},
formats:{
AccessToken :'jwt',
customizers: { jwt: async(ctx, token, jwt)=>{
jwt.payload.aud = jwt.payload.iss
}}
},
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: {
devInteractions: { enabled: false }, // defaults to true
resourceIndicators: {
enabled: true,
async useGrantedResource(ctx) {
return ctx.oidc.body && ctx.oidc.body.usegranted;
},
getResourceServerInfo(ctx, resource) {
if (resource.includes('wl')) {
return {
audience: resource,
scope: 'api:read api:write',
};
}
throw new errors.InvalidTarget();
},
defaultResource(ctx) {
if (ctx.oidc.body && ctx.oidc.body.nodefault) {
return undefined;
}
return 'urn:wl:default';
},
},
deviceFlow: { enabled: true }, // defaults to false
revocation: { enabled: true }, // defaults to false
},
jwks: {
keys: [/* keys left out for privacy*/]
},
};

This is working with me using resourceIndicators configuration that looks like:
resourceIndicators: {
enabled: true,
getResourceServerInfo: async (ctx, resourceIndicator, client) => {
return {
scope: 'api:read api:write',
audience: resourceIndicator,
accessTokenTTL: 2 * 60 * 60, // 2 hours
accessTokenFormat: 'jwt',
jwt: {
sign: { alg: 'RS256' },
},
}
}

Related

Node.js FCM token is empty but it's not

async function sendNotif(title, body0, token) {
return await admin.messaging().send({
message: {
token:
"dHUKMkIxRbS3uIpdnA1Qef:APA91bHQd2XUpFyWzfdbKrpPV2T9b0uJx9TfKZcyF-O_oAbQ13yA5R-52t_RTb_QSPrMpxw1OV9z8sNFRth5wGuCAld_9VsKr4oRdSWsMzqhrbKcTLC2rAp5QLOUALqiTadyvyvcjTmb!",
notification: {
title: title,
body: body0,
},
data: {
hello: "world",
click_action: "FLUTTER_NOTIFICATION_CLICK",
},
// Set Android priority to "high"
android: {
priority: "high",
},
// Add APNS (Apple) config
apns: {
payload: {
aps: {
contentAvailable: true,
},
},
headers: {
"apns-push-type": "background",
"apns-priority": "5", // Must be `5` when `contentAvailable` is set to true.
"apns-topic": "io.flutter.plugins.firebase.messaging", // bundle identifier
},
},
},
});
}
I am wondering why I am getting this error
errorInfo: {
code: 'messaging/invalid-payload',
message: 'Exactly one of topic, token or condition is required'
},
I checked the token and it's a good one i add firebase-admin library and I initialized it but it still didn't work .
so any answers?

Separate swagger into files

Hi guys i'm works with swagger ui v2, and has a problem with organization.
The current structure of my project is like this.
const swaggerDocument = {
swagger: "2.0",
info: {
version: "1.0.0",
title: "Hello",
description: "hello",
},
basePath: "/api",
tags: [
{
name: "Users",
description: "API for users in the system",
},
],
schemes: [
"https",
"http",
],
consumes: [
"application/json",
],
produces: [
"application/json",
],
paths: {
"/users": {
// ...
},
},
definitions: {
User: {
// ...
},
},
};
In my path there are several routes (many actually) that reference my definitions, example: " $ref: "#/definitions/Users"," and my swagger.ts file is gigantic, I would like a better solution to organize, I tried something below but it didn't work.
swagger.ts
import { user, userDefinitions } from './user';
const swaggerDocument = {
paths: {
"/users": {
...user,
},
},
definitions: {
User: {
...userDefinitions,
},
},
};
Can someone help me?
User.ts
const user = {
"/users": {
...
},
};
const userDefinitions = {
User: {
properties: {
...
},
},
};
export { user, userDefinitions };

How to get user information using node oidc provider

I got access token and I will pass access_token to userinfo endpoint it throwing an invalid token provided error How to fix this Issue. I tried to debug why this error is throwing we have validateAccessToken method(userinfo.js) in this method check the access_token is exist or not exist using this code await ctx.oidc.provider.AccessToken.find(accessTokenValue); when i print this result it's showing undefined
oidc_configuration.js
const oidc = new Provider('http://localhost:3000', {
clients: [
{
client_id: 'oidcCLIENT',
client_secret: '...',
grant_types: ['refresh_token', 'authorization_code'],
redirect_uris: ['http://sso-client.dev/providers/7/open_id', 'http://sso-client.dev/providers/8/open_id'],
}
],
interactions: {
url(ctx, interaction) { // eslint-disable-line no-unused-vars
return `/api/v1/open_id/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: {
devInteractions: { enabled: false }, // defaults to true
deviceFlow: { enabled: true }, // defaults to false
revocation: { enabled: true }, // defaults to false
},
jwks: {
keys: [
{
d: 'VEZOsY07JTFzGTqv6cC2Y32vsfChind2I_TTuvV225_-0zrSej3XLRg8iE_u0-3GSgiGi4WImmTwmEgLo4Qp3uEcxCYbt4NMJC7fwT2i3dfRZjtZ4yJwFl0SIj8TgfQ8ptwZbFZUlcHGXZIr4nL8GXyQT0CK8wy4COfmymHrrUoyfZA154ql_OsoiupSUCRcKVvZj2JHL2KILsq_sh_l7g2dqAN8D7jYfJ58MkqlknBMa2-zi5I0-1JUOwztVNml_zGrp27UbEU60RqV3GHjoqwI6m01U7K0a8Q_SQAKYGqgepbAYOA-P4_TLl5KC4-WWBZu_rVfwgSENwWNEhw8oQ',
dp: 'E1Y-SN4bQqX7kP-bNgZ_gEv-pixJ5F_EGocHKfS56jtzRqQdTurrk4jIVpI-ZITA88lWAHxjD-OaoJUh9Jupd_lwD5Si80PyVxOMI2xaGQiF0lbKJfD38Sh8frRpgelZVaK_gm834B6SLfxKdNsP04DsJqGKktODF_fZeaGFPH0',
dq: 'F90JPxevQYOlAgEH0TUt1-3_hyxY6cfPRU2HQBaahyWrtCWpaOzenKZnvGFZdg-BuLVKjCchq3G_70OLE-XDP_ol0UTJmDTT-WyuJQdEMpt_WFF9yJGoeIu8yohfeLatU-67ukjghJ0s9CBzNE_LrGEV6Cup3FXywpSYZAV3iqc',
e: 'AQAB',
kty: 'RSA',
n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ',
p: '5wC6nY6Ev5FqcLPCqn9fC6R9KUuBej6NaAVOKW7GXiOJAq2WrileGKfMc9kIny20zW3uWkRLm-O-3Yzze1zFpxmqvsvCxZ5ERVZ6leiNXSu3tez71ZZwp0O9gys4knjrI-9w46l_vFuRtjL6XEeFfHEZFaNJpz-lcnb3w0okrbM',
q: '3I1qeEDslZFB8iNfpKAdWtz_Wzm6-jayT_V6aIvhvMj5mnU-Xpj75zLPQSGa9wunMlOoZW9w1wDO1FVuDhwzeOJaTm-Ds0MezeC4U6nVGyyDHb4CUA3ml2tzt4yLrqGYMT7XbADSvuWYADHw79OFjEi4T3s3tJymhaBvy1ulv8M',
qi: 'wSbXte9PcPtr788e713KHQ4waE26CzoXx-JNOgN0iqJMN6C4_XJEX-cSvCZDf4rh7xpXN6SGLVd5ibIyDJi7bbi5EQ5AXjazPbLBjRthcGXsIuZ3AtQyR0CEWNSdM7EyM5TRdyZQ9kftfz9nI03guW3iKKASETqX2vh0Z8XRjyU',
use: 'sig',
}, {
crv: 'P-256',
d: 'K9xfPv773dZR22TVUB80xouzdF7qCg5cWjPjkHyv7Ws',
kty: 'EC',
use: 'sig',
x: 'FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4',
y: '_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4',
},
],
},
});
// Heroku has a proxy in front that terminates ssl, you should trust the proxy.
oidc.proxy = true;
const callback = oidc.callback();
How to fix this issue
You're running without a persistent adapter, meaning an in-memory one is used, are you possibly restarting your server after receiving the access token before calling the userinfo endpoint?
After completing the authentication, you will get access_token as well as id_token. I think you were using id_token in place of access_token which is why you are seeing that error
Instead use access_token then you can see the details

Feathers js - google login lead to delete of existing user

Steps to reproduce
1. Generate google access token in Frontend(I am using React library `react-google-login`)
2.Login using `local` strategy using email A(User A)
POST /authentication
body
{
"strategy": "local",
"email": "{{email A}}",
"password": "{{password}}"
}
3.Login as User B using `google` strategy of using jwt token(generated in step 2) in Authorization of account A
POST /authentication
body
{
"strategy": "google",
"access_token": "<google access token in step 1>"
}
Expected behavior
User B created.
Actual behavior
User B created, while User A is deleted in Database
Code
authentication.js
const { AuthenticationService, AuthenticationBaseStrategy, JWTStrategy } = require('#feathersjs/authentication');
const { LocalStrategy } = require('#feathersjs/authentication-local');
const { expressOauth } = require('#feathersjs/authentication-oauth');
const { discard, iff, isProvider, lowerCase, keep } = require('feathers-hooks-common')
const { OAuthStrategy } = require('#feathersjs/authentication-oauth');
class GoogleStrategy extends OAuthStrategy {
async getEntityData(profile) {
// this will set 'googleId'
const baseData = await super.getEntityData(profile);
const existingOauthEntity = await super.findEntity(profile)
// Check if user already exist
if(existingOauthEntity){
return {
...baseData
}
}else{
return {
...baseData,
email: profile.email,
};
}
}
}
module.exports = app => {
const authentication = new AuthenticationService(app);
authentication.register('jwt', new JWTStrategy());
authentication.register('local', new LocalStrategy());
authentication.register('google', new GoogleStrategy());
app.use('/authentication', authentication);
app.configure(expressOauth());
app.service('authentication').hooks({
before: {},
after: {
create: [
discard('authentication')
]
}
});
};
users.hook.js
const { iff, isProvider, keep, required, disallow, disablePagination, preventChanges, sequelizeConvert} = require('feathers-hooks-common');
const { authenticate } = require('#feathersjs/authentication').hooks;
const checkPermissions = require('feathers-permissions');
const errors = require('#feathersjs/errors');
const {
hashPassword, protect
} = require('#feathersjs/authentication-local').hooks;
module.exports = {
before: {
all: [
sequelizeConvert({
isEmailVerified: 'boolean',
}),
],
find: [
iff(isProvider('external'),
authenticate('jwt'),
checkPermissions({
roles: [ 'superadmin', 'admin'],
error: true,
})
)
],
get: [
iff(isProvider('external'),
iff(!(context => {return (context.params.authStrategies[0] === 'google')}),
authenticate('jwt'),
checkPermissions({
roles: [ 'admin'],
error: true,
})
)
)
],
create: [
iff(isProvider('external'),
iff((context => !context.params.authStrategies) || (context => !(context.params.authStrategies[0] === 'google')),
authenticate('jwt'),
checkPermissions({
roles: [ 'admin'],
error: true,
}),
required('email', 'password'),
hashPassword('password'),
)
)
],
update: [disallow()],
patch: [
iff(isProvider('external'),
preventChanges(true, [
'id',
'password',
'passwordResetEmailToken',
'passwordResetToken',
'passwordResetRequestedAt',
'passwordResetExpiresAt',
]),
authenticate('jwt'),
checkPermissions({
roles: ['admin'],
error: false,
}),
)
],
},
after: {
all: [
sequelizeConvert({
isEmailVerified: 'boolean',
}),
protect(
'password',
'passwordResetEmailToken'
)
],
find: [],
get: [],
create: [],
update: [disallow('external')],
patch: [],
remove: []
},
};

How to prevent overloading JWT token

i'm using jwt token with node.js npm jsonwebtoken in order to manage my users authentication. above that i'm adding to the payload the user permissions which contains the user read and write to which section.
like so:
const payload = {
user: {
id: user.id,
permissions: user.account_permissions
}
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: TIME},
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
The account_permissions object looks somthing like that:
const permissions = {
platform: 'Management',
sections: [
{
sectionName: 'setup',
read: true,
write: true
},
{
sectionName: 'chat',
read: true,
write: true
},
{
sectionName: 'maintenence',
read: true,
write: true
},
{
sectionName: 'classes',
read: true,
write: true
},
{
sectionName: 'income',
read: true,
write: true
},
{
sectionName: 'announcements',
read: true,
write: true
},
{
sectionName: 'messages',
read: true,
write: true
}
],
school: shoolID
};
My question is ; every user can have many permissions objects to different platforms which in that case makes the jwt token to be very large (sometimes to big to be even sent in the header).
I have a middleware that checks the user permissions in every route, im trying to avoid saving that data in a database in order to save database calls. how can i avoid such an overloading.

Resources