FeathersJS authentication using client certificate - node.js

I'm trying to create my own authentication strategy that reads the client's PKI certificate within a FeathersJS backend. This is handled in a before hook and based on the documentation hooks are
A hook is transport independent, which means it does not matter if it has been called through HTTP(S) (REST), Socket.io, Primus or any other transport Feathers may support in the future. They are also service agnostic, meaning they can be used with ​any​ service regardless of whether they have a model or not.
This is not a bad idea, however I need the TLS socket structure within the hook to get the user's certificate. Essentially calling: req.socket.getPeerCertificate(). I'm using the passport-client-certificate module and here's the strategy in question:
class ClientCertStrategy extends Strategy {
constructor (options, verify) {
if (typeof options === 'function') {
verify = options
options = {}
}
if (!verify) throw new Error('Client cert authentication strategy requires a verify function')
super()
this.name = 'client-cert'
this._verify = verify
this._passReqToCallback = options.passReqToCallback
}
_verified (err, user) {
if (err) { return this.error(err) }
if (!user) { return this.fail() }
this.success(user)
}
authenticate (req, options) {
// Requests must be authorized
// (i.e. the certificate must be signed by at least one trusted CA)
if (!req.socket.authorized) {
this.fail()
return
}
// This is where it fails! req.socket does not exist
const clientCert = req.socket.getPeerCertificate()
if (!clientCert) {
this.fail()
// TODO: Failure message
// this.fail({message: options.badRequestMessage || 'Missing client certificate'}, 400)
return
}
try {
if (this._passReqToCallback) {
this._verify(req, clientCert, this._verified.bind(this))
} else {
this._verify(clientCert, this._verified.bind(this))
}
} catch (err) {
return this.error(err)
}
}
}
Based on the FeathersJS code, the authenticate function basically makes a new request object from the hook. Is there any way to get the user's certificate earlier and make it available later on when the hook is executed?

I wrote an issue and was pointed to the FAQ which ultimately helped me solve this:
https://github.com/feathersjs/authentication/issues/693
https://docs.feathersjs.com/faq/readme.html#how-do-i-access-the-request-object-in-hooks-or-services
I ended up writing a middleware that stuck the certificate into the request params. The request params are copied into the hook which is then passed into the Passport strategy.

Related

Service to service authentication in (hapi+molecular) NodeJS

I have different microservices developed in Hapi+Molecular.
I used hapi-moleculer npm module to add molecular in hapi, I am using redis as transported to communicate between services.
I can call functions of service A from service B...
what i need is to add authentication to call functions of other services.
Like if Service A calling function of Service B it needs to authenticate to prevent others from connecting to my services.
I am calling servies like this
request.broker.call('users.logout', { });
I saw a module imicros-auth for this but i didn't found it much useful is there anyother module which can do this or is there any better approach to custom code for service to service authentication.
It should be like
If service is calling its own function, then no auth required, if calling function of other service then it must be authenticated
One more thing it should not be like fetching auth from db or some kind of this which makes response of service slow, can be token based or something like this
Maybe this middleware? https://github.com/icebob/moleculer-protect-services
To use this, you should generate a JWT token with service name for all services and define a list of the permitted services. The middleware will validate the JWT.
Here is the source of the middleware:
const { MoleculerClientError } = require("moleculer").Errors;
module.exports = {
// Wrap local action handlers (legacy middleware handler)
localAction(next, action) {
// If this feature enabled
if (action.restricted) {
// Create new handler
return async function ServiceGuardMiddleware(ctx) {
// Check the service auth token in Context meta
const token = ctx.meta.$authToken;
if (!token)
throw new MoleculerClientError("Service token is missing", 401, "TOKEN_MISSING");
// Verify token & restricted services
// Tip: For better performance, you can cache the response because it won't change in runtime.
await ctx.call("guard.check", { token, services: action.restricted })
// Call the original handler
return await next(ctx);
}.bind(this);
}
// Return original handler, because feature is disabled
return next;
},
// Wrap broker.call method
call(next) {
// Create new handler
return async function(actionName, params, opts = {}) {
// Put the service auth token in the meta
if (opts.parentCtx) {
const service = opts.parentCtx.service;
const token = service.schema.authToken;
if (!opts.meta)
opts.meta = {};
opts.meta.$authToken = token;
}
// Call the original handler
return await next(actionName, params, opts);
}.bind(this);
},
};

Auth0 access control

I am using Auth0 to manage a large set of users across several different applications with some being web based and others desktop and mobile. Under the meta data for each user I have an array of applications each user can access, I wondered how I might check this when authenticating so that access would be refused if not within that list.
I can do this very easily on the applications, however it would be great to do it on Auth0.
Using a Rule defined as follows has provided me with the functionality I was looking for:
function (user, context, callback) {
// ACL object
var acl = {
"someAppName": [ 'user1#mail.com', 'user2#mail.com' ],
"otherApp": ['user2#mail.com']
}
// if App is not in the ACL, skip
if(!acl.hasOwnProperty(context.clientName)){
return callback(null, user, context);
}
// check if user has access to app
var userHasAccess = acl[context.clientName].some(
function (email) {
return email === user.email;
}
);
if (!userHasAccess) {
return callback(new UnauthorizedError('Access denied.'));
}
callback(null, user, context);
}

Is there a way to prevent users from editing the local storage session?

I am creating a relational blog where I make use of ember_simple_auth:session to store the session like
{"authenticated":{"authenticator":"authenticator:devise","token":"rh2f9iy7EjJXESAM5koQ","email":"user#example.com","userId":1}}
However, on the developer tools on Chrome (and possibly on other browsers), it is quite easy to edit the email and userId in order to impersonate another user upon page reload.
EDIT #1
From the conversation with Joachim and Nikolaj, I now realized that the best way to tackle this problem is to probe the localStorage authenticity every time I need it (which is only on page reload) instead of attempting to prevent edits.
In order to validate authenticity, I create a promise that must be solved before the AccountSession can be used. The promise serverValidation() requests to create a token model with the current localStorage info, and when the server gets it, it validates the info and responds 200 with a simple user serialization with type as token if the information is legit. You can check more info on the Source Code.
Session Account
import Ember from 'ember';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend ({
session: service('session'),
store: service(),
serverValidation: false,
// Create a Promise to handle a server request that validates the current LocalStorage
// If valid, then set SessionAccount User.
loadCurrentUser() {
if (!Ember.isEmpty(this.get('session.data.authenticated.userId'))) {
this.serverValidation().then(() => {
return new RSVP.Promise((resolve, reject) => {
const userId = this.get('session.data.authenticated.userId');
// Get User to Session-Account Block
if(this.get('serverValidation') === true) {
return this.get('store').find('user', userId).then((user) => {
this.set('user', user);
resolve();
}).catch((reason) => {
console.log(reason.errors);
var possible404 = reason.errors.filterBy('status','404');
var possible500 = reason.errors.filterBy('status','500');
if(possible404.length !== 0) {
alert('404 | Sign In Not Found Error');
this.get('session').invalidate();
}
else if(possible500.length !== 0) {
alert('500 | Sign In Server Error');
this.get('session').invalidate();
}
reject();
});
}
else{
alert('Session for Server Validation failed! Logging out!');
this.get('session').invalidate();
resolve();
}
});
});
} else {
// Session is empty...
}
},
serverValidation() {
return new RSVP.Promise((resolve) => {
var tokenAuthentication = this.get('store').createRecord('token', {
id: this.get('session.data.authenticated.userId'),
email: this.get('session.data.authenticated.email'),
authenticity_token: this.get('session.data.authenticated.token'),
});
tokenAuthentication.save().then(() => {
this.set('serverValidation',true);
console.log('Server Validation complete with 200');
resolve();
}).catch((reason) => {
this.set('serverValidation',false);
resolve();
});
});
}
});
Token Controller
# Users Controller: JSON response through Active Model Serializers
class Api::V1::TokensController < ApiController
respond_to :json
def create
if token_by_id == token_by_token
if token_by_email == token_by_id
render json: token_by_id, serializer: TokenSerializer, status: 200
else
render json: {}, status: 404
end
else
render json: {}, status: 404
end
end
private
def token_by_id
User.find(user_params[:id])
end
def token_by_email
User.find_by(email: user_params[:email])
end
def token_by_token
User.find_by(authentication_token: user_params[:authenticity_token])
end
def user_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params.to_unsafe_h)
end
end
There is no way to prevent a user from editing the content of his local storage, session storage, or cookies.
But this should not worry you. The user is identified through the value of the token. The token is generated and sent to him by the authenticator when he logs in. To impersonate another user by editing the session data he would have to know that the other user is logged in, and know the token of that user.
Token is already signed on the server side, a standard JWT mechanism.
Having said that, there can be a couple of ways to check tempering in local storage:
Generate a token the way you already do.
Generate a random secret key to be kept on the server.
Generate a corresponding HMAC using this secret key.
Send the token + HMAC to the user.
When the user sends you this token, first check if HMAC is correct, if not then reject the token right away.
If HMAC is correct, validate the token the way you already do.
Another way:
Along with the token, a HMAC checksum too can be stored separately, and when sent back to the server by the client, check if checksum matches.

Best approach to authenticate client-certificate

What is the best approach to validate the client certificate supplied by the user?
i am using express.js and validating certificates using client-certificate-auth.
at the moment i am checking the certificate fingerprint, something like this:
var checkAuth = function (cert) {
if (cert.fingerprint === 'XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX') {
return true;
} else {
return false;
}
};
is this the best approach? or am i doing something wrong?
Thanks.

OpenID OWIN auth and lack of user permissions

I may be handling this totally incorrect, but I am using OpenID with MS Azure to authentication my users, then I check to make sure the user has a user account in the notifications of the OpenID middleware, if the user is not found, I am throwing a security exception. How do I return a You do not have access to this applicaiton type page. Am I just missing the hook?
Here is the example:
https://gist.github.com/phillipsj/3200ddda158eddac74ca
You can use try...catch inside the notifications, something along these lines:
SecurityTokenValidated = (context) =>
{
try
{
// retriever caller data from the incoming principal
var username = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value.Split('#')[0];
var database = DependencyResolver.Current.GetService(typeof (IDatabase)) as IDatabase;
var employee = database.Query(new GetEmployeeByUsername(username));
if (employee == null)
{
throw new SecurityTokenValidationException();
}
// I add my custom claims here
context.AuthenticationTicket.Identity.AddClaims(claims);
return Task.FromResult(0);
}
catch (SecurityTokenValidationException ex)
{
context.HandleResponse(); // This will skip executing rest of the code in the middleware
context.Response.Redirect(....);
return Task.FromResult(0);
}
}

Resources