Auth0 access control - node.js

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

Related

How can I edit the backend of firebase cloud function extension?

I am coding a project similar to patreon.com and my goal is to let multiple members create their own subscription plans and sell it.
I came across a firebase extension for stripe payments - https://firebase.google.com/products/extensions/stripe-firestore-stripe-payments
The problem with this extension is that I can only create a 1 premium membership that adds custom claims to the auth object and I can validate it like this:
export default async function isUserPremium(): Promise<boolean> {
await auth.currentUser?.getIdToken(true);
const decodedToken = await auth.currentUser?.getIdTokenResult();
return decodedToken?.claims?.stripeRole ? true : false;
}
That means that even if I have 100 different subscriptions, I can only attach single boolean value, which is useless.
I went to the source code and I found this snippet:
// Update their custom claims
if (role) {
try {
// Get existing claims for the user
const { customClaims } = await admin.auth().getUser(uid);
// Set new role in custom claims as long as the subs status allows
if (['trialing', 'active'].includes(subscription.status)) {
logs.userCustomClaimSet(uid, 'stripeRole', role);
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: role });
} else {
logs.userCustomClaimSet(uid, 'stripeRole', 'null');
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: null });
}
} catch (error) {
// User has been deleted, simply return.
return;
}
}
I don't fully understand this code, but I think this is where the boolean value is assigned.
Would it be possible to somehow edit this source code, so that instead of boolean value, I could store subscription plan ids in Array , so that in the front end I could validate and allow customer to access users content only if he has active plan in that array
?

Access Facebook Messenger User Profile API in DialogFlow

I'm building a cross-platform chatbot in Google's DialogFlow. I'd like to access the Facebook User Profile API to learn the user's first name.
I'm struggling to find advice on how (or if) I can make this happen.
https://developers.facebook.com/docs/messenger-platform/identity/user-profile/
Has anybody here achieved this?
I did that for one of my bots yesterday, you need 2 things, first the Page Token and second is the psid which is Page scope user ID.
On dialogflow, you will receive the request block with psid as sender id. You can find it at:
agent.originalRequest.payload.data.sender.id
This psid needs to be passed to api get request at
/$psid?fields=first_name with your page Token as accessToken to get the first name in response.
You need to make a call to Facebook Graph API in order to get user's profile.
Facebook offers some SDKs for this, but their official JavaScript SDK is more intended to be on a web client, not on a server. They mention some 3rd party Node.js libraries on that link. I'm particularly using fbgraph (at the time of writing, it's the only one that seems to be "kind of" maintained).
So, you need a Page Token to make the calls. While developing, you can get one from here:
https://developers.facebook.com/apps/<your app id>/messenger/settings/
Here's some example code:
const { promisify } = require('util');
let graph = require('fbgraph'); // facebook graph library
const fbGraph = {
get: promisify(graph.get)
}
graph.setAccessToken(FACEBOOK_PAGE_TOKEN); // <--- your facebook page token
graph.setVersion("3.2");
// gets profile from facebook
// user must have initiated contact for sender id to be available
// returns: facebook profile object, if any
async function getFacebookProfile(agent) {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
let payload;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
if ( fbSenderID ) {
try { payload = await fbGraph.get(fbSenderID) }
catch (err) { console.warn( err ) }
}
return payload;
}
Notice you don't always have access to the sender id, and in case you do, you don't always have access to the profile. For some fields like email, you need to request special permissions. Regular fields like name and profile picture are usually available if the user is the one who initiates the conversation. More info here.
Hope it helps.
Edit
Promise instead of async:
function getFacebookProfile(agent) {
return new Promise( (resolve, reject) => {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
fbGraph.get( fbSenderID )
.then( payload => {
console.log('all fine: ' + payload);
resolve( payload );
})
.catch( err => {
console.warn( err );
reject( err );
});
});
}

What is the right way to determine user roles and grant API access in Node.js Web-Server?

I have a Web-server with REST APIs in Node.js-express setup. I have different APIs exposed and i use OAuth to authenticate users. Once i identify user i retrieve his role and decide to grant permission for an API. The problem is all my APIs have code repeated and all of them have to check the role and determine the permissions. Is there any better way to do this? Should i look at ACL? In general i am novice in Web-services and may not be aware of best solutions. Thanks.
Example code :
var accessCallback = function (err, user) {
if(err == null) {
var userRole = getUserRole (user.role_id);
if(userRole == ROLE.SUPERVISOR || userRole == ROLE.MANAGER || userRole == ROLE.EMPLOYEE){
doStuff(); //business logic
}
else {
response.status(400).send (error.UserAccessDeniedError());
}
}
else {
response.status(500).send(error.DatabaseError(err));
}
};
access.determineUser (req.body.tokenID, accessCallback);
What you could do is declare a list of roles that allow the user, and check if the user's role is in that list:
var userRole = getUserRole(user.role_id);
function foo(error, user) {
if(error == null) {
var authorizedRoles = [
ROLE.SUPERVISOR,
ROLE.MANAGER
];
if(authorizedRoles.indexOf(userRole) > -1) {
//Authorized
} else {
//Error
}
} //...
Then, you can configure the authorized roles of each of your API point, and even put the verification in a middleware function.

Microsoft Graph API Permissions for non-admins?

I am trying to create a dropdown with all the users in my Office365 tenant. I created an app in Azure AD and gave it all the necessary permissions. I gave it all the permissions for Microsoft Graph actually, app and delegated. All of them.
Then I wrote up my script to query all users with https://graph.microsoft.com/v1.0/users.
I had my tenant admin go in and accept the permissions then output the list of users in the UI. Works fine for the admin
I'm not an admin but when I go to the page I get the following error:
This application requires application permissions to another
application. Consent for application permissions can only be performed
by an administrator. Sign out and sign in as an administrator or
contact one of your organization's administrators.
I need to know if this will work for users with even lower permissions. From what I understand the API request and the App is running under the permissions given to the application in Azure. So even if the user as Read Only, the request isn't running under the user, it's running under the Application I set up. So why would I get the error regarding permissions?
This is the code I'm using:
(function () {
"use strict";
// Some samples will use the tenant name here like "tenant.onmicrosoft.com"
// I prefer to user the subscription Id
var subscriptionId = "metenant.onmicrosoft.com";
// Copy the client ID of your AAD app here once you have registered one, configured the required permissions, and
// allowed implicit flow https://msdn.microsoft.com/en-us/office/office365/howto/get-started-with-office-365-unified-api
var clientId = "cccb1f2f-xxx-x-xxxxx-x-x-x-x-x-";
window.config = {
// subscriptionId: subscriptionId,
clientId: clientId,
postLogoutRedirectUri: window.location.origin,
endpoints: {
graphApiUri: 'https://graph.microsoft.com'
},
cacheLocation: 'localStorage' // enable this for IE, as sessionStorage does not work for localhost.
};
var authContext = new AuthenticationContext(config);
// Check For & Handle Redirect From AAD After Login
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// If not logged in force login
var user = authContext.getCachedUser();
// NOTE: you may want to render the page for anonymous users and render
// a login button which runs the login function upon click.
if (!user) authContext.login();
// Acquire token for Files resource.
authContext.acquireToken(config.endpoints.graphApiUri, function (error, token) {
// Handle ADAL Errors.
if (error || !token) {
console.log('ADAL error occurred: ' + error);
return;
}
// Execute GET request to Files API.
var filesUri = config.endpoints.graphApiUri + "/v1.0/users";
$.ajax({
type: "GET",
url: filesUri,
headers: {
'Authorization': 'Bearer ' + token,
}
}).done(function (response) {
console.log('Successfully fetched from Graph.');
console.log(response);
var container = $(".container")
container.empty();
$.each(response.value, function(index, item) {
container.append($('<li>').text(item.displayName + " " + item.mail + " " + item.mobilePhone))
})
}).fail(function (response) {
var err = JSON.parse(response.responseText)
console.log('Failed:', err.error.message);
});
});
})();
There are two kinds of permission/scope for Microsoft Graph. One is that require administrator’s consent. The other is not required.
What’s the permission you were config for this app? To list the users without administrator’s consent, we can use the scope User.ReadBasic.All like figure below:
You can get more detail about the permission/scope from here.
Modify:
At present, the adal.js doesn’t provide the admin consent. If you want to use this feature, you can modify the code to add a prameter like below:
AuthenticationContext.prototype.login = function (prompt) {
// Token is not present and user needs to login
var expectedState = this._guid();
this.config.state = expectedState;
this._idTokenNonce = this._guid();
this._logstatus('Expected state: ' + expectedState + ' startPage:' + window.location);
this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST, window.location);
this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR, '');
this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN, expectedState);
this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN, this._idTokenNonce);
this._saveItem(this.CONSTANTS.STORAGE.FAILED_RENEW, '');
this._saveItem(this.CONSTANTS.STORAGE.ERROR, '');
this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION, '');
var urlNavigate = this._getNavigateUrl('id_token', null) + '&nonce=' + encodeURIComponent(this._idTokenNonce);
if (prompt && prompt === "admin_consent") {
urlNavigate = urlNavigate + "&prompt=admin_consent"
}
this.frameCallInProgress = false;
this._loginInProgress = true;
if (this.config.displayCall) {
// User defined way of handling the navigation
this.config.displayCall(urlNavigate);
} else {
this.promptUser(urlNavigate);
}
// callback from redirected page will receive fragment. It needs to call oauth2Callback
};
And if you were using Angular, we also need to modify the adal-angular.js:
this.$get = ['$rootScope', '$window', '$q', '$location', '$timeout', function ($rootScope, $window, $q, $location, $timeout) {
...
return {
// public methods will be here that are accessible from Controller
config: _adal.config,
login: function (prompt) {
_adal.login(prompt);
},
...
}
Then we can provide two button for users login in. One button is for the users sign-in with themselves. And the other is for admin to give the consent for the organization. Here is the code redirect to the login page for the admin consent in the control of Angular:
$scope.login = function () {
adalService.login("admin_consent");
};

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