Service to service authentication in (hapi+molecular) NodeJS - node.js

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);
},
};

Related

Azure Functions app + Auth0 provider, getting 401 when calling API with auth token

I have read, and implemented local dev projects to match, Auth0's Complete Guide To React User Authentication with Auth0, successfully. I am confident in the implementation, given that all aspects of login and route protection are working correctly, as well as the local express server successfully authenticating API calls that use authentication tokens generated via the Auth0 React SDK.
I have added third button to the sample project's external-apis.js view for use in calling another API that I am trying to integrate with, which is an Azure Functions app. I would like to use Auth0 for this API in the same way I do for the express server, and take advantage of Azure's "Easy Auth" capabilities, as discussed in this MS doc. I have implemented an OpenID Connect provider, which points to my Auth0 application, in my Azure Function app per this MS doc.
This is what the function that calls this Azure Function app API looks like:
const callAzureApi = async () => {
try {
const token = await getAccessTokenSilently();
await fetch(
'https://example.azurewebsites.net/api/ExampleEndPoint',
{
method: 'GET',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${token}`,
},
}
)
.then((response) => response.json())
.then((response) => {
setMessage(JSON.stringify(response));
})
.catch((error) => {
setMessage(error.message);
});
} catch (error) {
setMessage(error.message);
}
};
My issue is that making calls to this Azure Function app API always returns a 401 (Unuthorized) response, even though the authorization token is being sent. If I change the Authorization settings in the Azure portal to not require authentication, then the code correctly retrieves the data, so I'm confident that the code is correct.
But, is there something else I have missed in my setup in order to use Auth0 as my authentication provider for the backend in Azure?
Through continued documentation and blog reading, I was able to determine what was missing from my original implementation. In short, I was expecting a little too much after reading about tge "Easy Auth" features of Azure, at least when using an OpenID Connect provider like Auth0. Specifically, the validation of the JSON Web Token (JWT) does not come for free, and needed further implementation.
My app is using the React Auth0 SDK to sign the user in to the identity provider and get an authorization token to send in its API requests. The Azure documentation for client-directed sign-in flow discusses the ability to validate a JWT using a specific POST call to the auth endpoint with the JWT in the header, but even this feature seems out of reach here, given that OpenID Connect is not listed in the provider list, and my attempts at trying it anyway continued to yield nothing but 401s.
The answer, then, was to implement the JWT validation directly into the Azure function itself, and return the proper response only when the JWT in the request header can be validated. I would like to credit blog posts of Boris Wilhelm and Ben Chartrand for helping to get to this final understanding of how to properly use Auth0 for an Azure Functions backend API.
I created the following Security object to perform the token validation. The static nature of the ConfigurationManager is important for caching the configuration to reduce HTTP requests to the provider. (My Azure Functions project is written in C#, as opposed to the React JS front-end app.)
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
namespace ExampleProject.Common {
public static class Security {
private static readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
private static readonly string ISSUER = Environment.GetEnvironmentVariable("Auth0Url", EnvironmentVariableTarget.Process);
private static readonly string AUDIENCE = Environment.GetEnvironmentVariable("Auth0Audience", EnvironmentVariableTarget.Process);
static Security()
{
var documentRetriever = new HttpDocumentRetriever {RequireHttps = ISSUER.StartsWith("https://")};
_configurationManager = new ConfigurationManager<OpenIdConnectConfiguration> (
$"{ISSUER}.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
documentRetriever
);
}
public static async Task<ClaimsPrincipal> ValidateTokenAsync(AuthenticationHeaderValue value) {
if(value?.Scheme != "Bearer")
return null;
var config = await _configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameter = new TokenValidationParameters {
RequireSignedTokens = true,
ValidAudience = AUDIENCE,
ValidateAudience = true,
ValidIssuer = ISSUER,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKeys = config.SigningKeys
};
ClaimsPrincipal result = null;
var tries = 0;
while (result == null && tries <= 1) {
try {
var handler = new JwtSecurityTokenHandler();
result = handler.ValidateToken(value.Parameter, validationParameter, out var token);
} catch (SecurityTokenSignatureKeyNotFoundException) {
// This exception is thrown if the signature key of the JWT could not be found.
// This could be the case when the issuer changed its signing keys, so we trigger
// a refresh and retry validation.
_configurationManager.RequestRefresh();
tries++;
} catch (SecurityTokenException) {
return null;
}
}
return result;
}
}
}
Then, I added this small bit of boilerplate code toward the top of any HTTP-triggered functions, before any other code is run to process the request:
ClaimsPrincipal principal;
if ((principal = await Security.ValidateTokenAsync(req.Headers.Authorization)) == null) {
return new UnauthorizedResult();
}
With this in place, I finally have the implementation I was looking for. I'd like to improve the implementation with something more generic like a custom attribute, but I'm not sure that's possible yet either for OpenID Connect providers. Still, this is a perfectly acceptable solution for me, and gives me the level of security I was looking for when using a React front-end with an Azure Functions back-end.
Cheers!

Error occuriring with call to user authenticated http trigger

I am using Azure Functions v3
I am trying to use Authentication and I have set my function to User level security for its HttpTriggers
The logic below is called on the startup of my function
protected override void SetupAuthentication(
IServiceCollection services, IConfiguration configuration)
{
var tokenOptions = configuration.GetSection("JwtIssuerOptions")
.Get<TokenConfiguration>();
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = tokenOptions.SecurityKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = tokenOptions.Issuer,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = tokenOptions.Audience,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
services.Configure<IdentityConfiguration>(configuration.GetSection("IdentityConfiguration"));
services.AddScoped<CustomJwtBearerEvents>();
services
.AddAuthentication(o =>
{
o.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.EventsType = typeof(CustomJwtBearerEvents);
});
}
When I call the function externally I get the error
No authentication handler is registered for the scheme 'WebJobsAuthLevel'.
The registered schemes are: Bearer. Did you forget to call AddAuthentication().AddSomeAuthHandler?.
What have I missed?
I need to mimic the same convention as web apps
[FunctionName("GetPayments")]
public async Task<List<PaymentDto>> GetPaymentsAsync(
[HttpTrigger(AuthorizationLevel.User, "post", Route = "payments/get-payments")]
HttpRequest req,
ILogger log)
{
var data = await req.ReadAsStringAsync();
//THis is where I have my logic which I only want to be able to access if the user has permissions
}
I have seen the link below
https://damienbod.com/2020/09/24/securing-azure-functions-using-azure-ad-jwt-bearer-token-authentication-for-user-access-tokens/comment-page-1/?unapproved=127819&moderation-hash=3fdd04b596812933c4c32e8e8c8cf26a#comment-127819
It initially looked to be what I need, but I cant work out how to adapt it so that it just uses the identity token validation side
Any help would be appreciated
Paul
Have a look at: https://www.nuget.org/packages/DarkLoop.Azure.Functions.Authorize
The latest version ensures all built-in authentication is in place before you add your own or extend the built-in ones. By the way JTW Bearer is already configured by the Functions runtime.
All you need to do is call in your method
services.AddFunctionsAuthentication();
services.AddFunctionsAuthorization();
and then chain whatever other schemes you need to configure after the AddFunctionsAuthentication() call. The resulting authentication builder is designed to handle modifications to the jwt bearer (AddJwtBearer(options => ...) and will not break telling you the Bearer scheme already exists.
This package also gives you the ability to use the FunctionsAuthorize attribute to handle granular authorization requirements for your HTTP functions. Here is a blog post with details: https://blog.darkloop.com/post/functionauthorize-for-azure-functions-v3

Validating an access token in nodejs resource server with Outh server uri in spring-boot

I have OAuth2 server[Spring-boot] which validates the clients with password credentials method.
The access token obtained by the Frontend client[Vuejs] uses the token to access the Resource server[Spring-boot].
Here, while the access token is passed by Frontend client to the resource server, the resource server cross-validated it with the OAuth2 server with the following code
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfigRemoteTokenService extends ResourceServerConfigurerAdapter {
#Primary
#Bean
public RemoteTokenServices tokenServices() {
final RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("https://localhost:9088/oauth/check_token");
tokenService.setClientId("fooClientIdPassword");
tokenService.setClientSecret("password");
return tokenService;
}
}
Now, I m trying to implement the same with nodejs as I planned to split a particular functionality which produces overhead to the resource server written in spring-boot.
I don't know how to implement the cross validation mechanism in nodejs like the below code.
tokenService.setCheckTokenEndpointUrl("https://localhost:9088/oauth/check_token");
try the express-oauth2-bearer library it works fine
first of all install it:
npm i express-oauth2-bearer --save
and use this code in your client :
The library needs the following values to authroize requests:
Issuer Base URL: The base URL of the authorization server. If you're using Auth0, this is your tenant Domain pre-pended with https:// (like https://tenant.auth0.com) found on the Settings tab for your Application in the Auth0 dashboard.
Allowed Audiences: Audience identifier (or multiple separated by a comma) allowed for the access token. If you're using Auth0, this is the Identifier found on the Settings tab for your API in the Auth0 dashboard.
These can be configured in a .env file in the root of your application:
# .env
ISSUER_BASE_URL=https://YOUR_DOMAIN
ALLOWED_AUDIENCES=https://api.yourapplication.com
... or in your application code:
app.use(auth({
issuerBaseURL: 'https://tenant.auth0.com',
allowedAudiences: 'https://api.yourapplication.com'
}));
The OpenID strategy is the default strategy for token validation. With the configuration values set in the .env file, the following code will restrict requests to all proceeding routes to ones that have a valid access token with the https://api.yourapplication.com audience and the read:products scope:
const { auth, requiredScopes } = require('express-oauth2-bearer');
app.use(auth());
app.get('/products',
requiredScopes('read:products'),
(req, res) => {
console.dir(req.auth.claims);
res.sendStatus(200);
});
If access tokens are not expected to be signed like OpenID Connect ID tokens, add the auth middleware with a callback to validate as follows:
const { auth, requiredScopes } = require('express-oauth2-bearer');
const validateAccesToken = async (token) => {
const token = await db.tokens.find(token);
if (token.expired) { return; }
return token;
};
app.use(auth(validateAcessToken)));
app.get('/products',
requiredScopes('read:products'),
(req, res) => {
console.dir(req.auth.claims);
res.sendStatus(200);
});

FeathersJS authentication using client certificate

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.

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