Using App ID to Manage Access to Your App's Sensitive Data

5 min read

With IBM Cloud™ App ID, you can define which users are able to access your sensitive data, use specific features, or perform specific actions in your apps.

Ensuring that the correct people have the approved access when they need it can be difficult when you are coding your application. Now, with IBM Cloud App ID, you can define which users are able to access your sensitive data, use specific features, or perform specific actions in your apps.

In this blog post, I'll walk you through how you can grant access to specific resources by defining runtime actions and assigning role-based permissions to your users. I'll also show how my application validates users' scopes to provide a different experience according to their role.

A scope is a runtime action in your application that you register with IBM Cloud App ID to create an access permission. A role is a collection of scopes that allow varying permissions to different types of app users. To control access, you can create scopes and group them into roles. Then, you can assign the role to one or more of your app users.

So, to show what that means, I'm using an elevator application as an example. I'll assign roles to the different types of users that allow each type of user to perform different operations. Specifically, I'll assign the roles Caller and Technician. A Caller is able to call the elevator and select a new floor only. The Technician role allows the user to perform more operations. For example, If I don't want just anyone to be able to stop or service the elevator; the permissions for those actions are assigned to the Technician only. 

r1

Before you begin

Before you can start creating roles and scopes, you need to be sure that you have the following prerequisites:

  • An instance of App ID.
  • An application.
  • App users. For this blog post, I created the users Bob, Peter, and Ted in Cloud Directory.

Want to follow along?

  • Download the sample app.
  • Add localhost:3000/* to your list of allowed redirect URIs. You can edit your allowed redirect URIs on the Manage authentication > Authentication settings page of the App ID dashboard.

Creating scopes and roles

Now that App ID is configured and I have the users that I need, let's walk through creating scopes and roles.

  1. Register your application with App ID by going to Applications > Add Application. I created an application called Elevators and I defined the scopes elevator.call, elevator.stop, and elevator.service to represent the specific operations that the different roles can perform:
    r2
  2. Create your roles by going to Roles and profiles > Roles > Create role. I created the roles Caller and Technician. I added only the elevator.call scope to the Caller role. 
    r3
    While for the Technician role, I gave the ability to complete more operations by adding the scopes elevator.call, elevator.stop, and elevator.service: 
    r4
  3. Assign the roles to specific application users by going to Roles and profiles > User profiles. Then, choose the user that you want to assign the role to and click the More options menu > Assign role. I assigned the Technician role to Ted and the Caller role to Peter. In the following image, you can see what it looks like when I assign the Technician role to Ted:
    r5

Note: I used the credentials of my elevator application in the configuration of my sample applications in localdev-config.json. To grab your credentials, open your application in the Applications page of the App ID dashboard.

Configuring your application's endpoints

After creating the roles and assigning them to users, I want my application to verify the users' scopes when performing certain operations. To do this, I will define some endpoints on my application that will either use App ID's WebApp Strategy or send a request to backend endpoints that I will also define, which will perform the validation by using App ID's API Strategy. 

WebAppStrategy (Web app endpoints)

Using the elevator app as an example, you can see how I secure the following endpoint by using the hasScope method in WebAppStrategy to check whether the access token contains the elevator.service and elevator.stop scopes:

// This endpoint will return true if we should show the technician menu.
// we show this menu if request's access token contains the scopes "elevator.stop" and "elevator.service".
app.get("/protected/api/shouldShowTechnicianMenu", (req, res) => {
 res.send(WebAppStrategy.hasScope(req, 'elevator.service elevator.stop'));
});

These three endpoints call backend server endpoints to validate that the access token contains the elevator.call, elevator.service, and elevator.stop scopes respectively:

// Call protected endpoint on backend, where we will check if the access token has the "elevator.call" scope.
app.get("/protected/callElevator", async function(req, res) {
 const options = {
  url: 'http://localhost:1234/protected/callElevator',
  headers: {
   'Authorization': 'Bearer ' + req.session[WebAppStrategy.AUTH_CONTEXT].accessToken
  }
 };
 try {
  await request(options);
  res.send('The elevator will arrive shortly.');
 }catch(e) {
  res.send("You don't have permissions to perform this action.");
 }
});


// Call protected endpoint on backend, where we will check if the access token has the "elevator.service" scope.
app.get("/protected/serviceMode", async function(req, res) {
 const options = {
  url: 'http://localhost:1234/protected/serviceMode',
  headers: {
   'Authorization': 'Bearer ' + req.session[WebAppStrategy.AUTH_CONTEXT].accessToken
  }
 };
 try {
  await request(options);
  res.send("Elevator is on service mode.");
 }catch(e) {
  res.send("You don't have permissions to perform this action.");
 }
});


// Call protected endpoint on backend, where we will check if the access token has the "elevator.stop" scope.
app.get("/protected/stopElevator", async function(req, res) {
 const options = {
  url: 'http://localhost:1234/protected/stopElevator',
  headers: {
   'Authorization': 'Bearer ' + req.session[WebAppStrategy.AUTH_CONTEXT].accessToken
  }
 };
 try{
  await request(options);
  res.send("Elevator is now stopped.");
 } catch (e) {
  res.send("You don't have permissions to perform this action.");
 }
});

APIStrategy (Backend endpoints)

These endpoints on the backend server use APIStrategy to validate that the access token contains the elevator.call, elevator.service, and elevator.stop scopes respectively:

app.get("/protected/callElevator",
 passport.authenticate(APIStrategy.STRATEGY_NAME, {
  audience: config.clientId,
  scope: "elevator.call",
  session: false
 }),
 function(req, res) {
  res.send('success');
 });
app.get("/protected/serviceMode",
 passport.authenticate(APIStrategy.STRATEGY_NAME, {
  audience: config.clientId,
  scope: "elevator.service",
  session: false
 }),
 function(req, res) {
  res.send('success');
 });
app.get("/protected/stopElevator",
 passport.authenticate(APIStrategy.STRATEGY_NAME, {
  audience: config.clientId,
  scope: "elevator.stop",
  session: false
 }),
 function(req, res) {
  res.send('success');
 });

Access control in action

r6

Signing in as Bob (no roles)

r7
r8

Bob doesn't have the Caller role, and specifically, he doesn't have a role that contains the elevator.call scope, so he is not able to call an elevator. When he tries to call the elevator, Bob receives a message saying he doesn't have the permissions that are required for this action:

r9

Viewing Bob's access token, we see it doesn't contain any of the scopes we defined for this application:

r10

Signing in as Peter (Caller role)

r11

When signing in with Peter (who has the Caller role), we land on the following page: 

r12

Peter has the caller role so he is able to call an elevator:

r13

Viewing Peter's access token, we see that it contains the elevator.call scope:

r14

Signing in as Ted (Technician role)

r15

The technician menu is only shown for users with the elevator.service and elevator.stop scopes. Ted has the Technician role that contains these scopes, so he is shown the Technician menu:

r16

Ted can call an elevator since he has the elevator.call scope as part of the Technician role:

r17

When you click on Stop Elevator or Start Service Mode, a request is sent to the backend server to validate that the access token contains the required scope. Ted's access token contains the elevator.stop scope. When he clicks the Stop Elevator button, his access token is validated by the server, which will allow us to show the text: Elevator is now stopped:

r18

Viewing Ted's access token, we see it contains the scopes elevator.call, elevator.stop, and elevator.service:

r19

That's it!

In this post, I showed you how to use App ID to manage users access permissions to your application's resources. Hopefully, now you feel confident in your ability to set up your App ID instance with scopes and roles and set up your application to manage access according to the roles that you assigned. 

Questions and feedback

We'd love to hear from you with feedback! Have questions, comments, or concerns? Let us know:

  • Reach out directly to the development team on Slack!
  • If you have technical questions about App ID, post your question on Stack Overflow and tag your question with ibm-appid.
  • For questions about the service and getting started instructions, use the IBM Developer Answers forum. Include the appid tag.
  • Open a support ticket in the IBM Cloud menu.

To get started with App ID, check it out in the IBM Cloud Catalog.

Be the first to hear about news, product updates, and innovation from IBM Cloud