Quantcast
Channel: Blog – Stormpath User Identity API
Viewing all articles
Browse latest Browse all 278

Helping Hands: Let One User Act On Behalf Of Another Securely

$
0
0

Helping Hands: Securely Allow One User To Act On Behalf Of Another

Authorizing a user to access protected resources in a secure manner is a common need in any web application—and one that Stormpath has been built to help you, as a developer, address.

But what about cases when your application needs to allow one user to access protected resources by impersonating another?

Facebook’s “View My Page As” feature is a great example of this: it displays how another user would see your profile, allowing you to verify that your privacy settings are properly configured and that another person sees only the content you've allowed.

How do you let the application know which user to “run as” securely? And how do you verify that the user running the application as someone else is who she says she is?

Those very questions came in to our support team, and we have a best practice approach you can apply in your own application architecture that we’d love to share.

(User) Story Time

Imagine BlogPortal, a CMS which determines whether a user can see content based on his or her role. A company is using BlogPortal for its intranet site to display inter-company news to employees and managers. “Managers” see all of the content; “Employees” see a subset.

In order to verify that managers and employees see the correct content, BlogPortal allows admin users to log in to the content portal on behalf of other users. This allows admins to verify that the content on the intranet site appears correctly for normal users.

The underlying web service of the CMS is powered by a REST API.

Tackling The Use Case

Now, let’s take a standard use case given the above. An admin user, Jane, wants to assume the identity of normal employee, John, so she can verify that John only sees the content he is supposed to see.

At the heart of this use case is the notion that a user can view resources based on his or her role. Each user has one role. In this case, Jane has the “admin” role and John the “employee” role.

Let’s assume that we’ve built our application in a manner that allows users to authenticate and access protected resources based on their roles. Both Jane and John can log into the application, and the appropriate role-based access control (RBAC) is in place to restrict view access respective to their roles.

How, then, do we allow Jane to temporarily assume the role of John? And how do we do so securely?

The Wrong Way

Given that the CMS is REST-based, it is supposed to be stateless by design. Thus, we wouldn’t want to take the following approach for handling Jane’s request:

  curl -u $jane:$janes_password \     
       -H "Accept: application/json" \      
       -H "Accept: application/json" \      
       -D "{'userIdToAssume' : '456'}" \      
       -X POST \      
       "https://company.blog.com/v1/users/456?assumeIdentity"

In this case, “456” is John’s user ID.

Sending the username of the user to mimic in plaintext with the request as above is just fine; without its password or shared secret, there’s no security risk.

The actual design presented above, though, is no bueno. With this design, the REST server must maintain state - the assumed identity - for Jane's future REST requests. While possible, this contradicts RESTful design principles of stateless architectures, and statelessness is fundamental to scalability. It may be 'secure', but it could be considered poor design.

The Right Way

Instead, we should define a custom HTTP header which is interpreted by the REST server so it can respond appropriately. This custom header, if present, would tell the service which user to mimic.

For example, consider Jane’s request to view all of John’s accessible posts:

     curl -u $jane:$jane_password \      
          -H "Accept: application/json" \      
          -H "X-Run-As: https://blogportal.company.com/v1/users/john" \      
          "https://company.blog.com/v1/posts"

Now the API controller handling the request can respond with the appropriate content by reading the new custom header:

    ON “GET /posts” DO function viewPosts(RequestObj request) {
	String runAs = request.getHeaders.get(“X-Run-As”);
	User requestUser = request.getUser();

	if (runAs != null) {
		//grab posts target user is allowed to see based on role
		User targetUser = getTargetUser(runAsIdentity);
		return blog.getPostsForUser(targetUser);
	} else {
		//grab posts current user is allowed to see based on role
		return blog.getPostsForUser(requestUser);
        }	
     }

So we’re done, right? Well, almost. One critical piece is missing: we didn’t actually check if the request is fully valid. We need to confirm that the user making the request is indeed authorized to assume the identity of the user specified:

    ON “GET /posts” DO function viewPosts(RequestObj request) {
	String runAs = request.getHeaders.get(“X-Run-As”);
	User requestUser = request.getUser();

	if (runAs != null) {
	  User targetUser = getTargetUser(runAsIdentity);
	  String runAsPermission = “user:” + targetUser.getId() + “:assumeIdentity”;
	  // (e.g., “user:john:assumeIdentity”);

	  if (getRequestUser(requestUser).isPermitted(runAsPermission) {
	    // grab posts target user is allowed to
// see based on role if allowed return blog.getPostsForUser(targetUser); } else { //401 return (HTTP.UNAUTHORIZED, “Unauthorized to assume identity”); } } else { //grab posts current user is allowed to see based on role return blog.getPostsForUser(requestUser); } }

Of course, there’s more to the actual implementation that you’ll need to get right in order for the whole design to work.

For instance, we’re assuming that the service’s REST API is properly secured and that Jane’s API access is valid based on the supplied credentials. We’d also like to assume the service would leverage a pre-existing permission framework, such as Apache Shiro, to avoid the need to manually write and integrate the isPermitted() function throughout the application.

You’ll also want to be very conscious about which API calls you allow when one user is impersonating another. For example, you may not want to allow Jane to post to the blog on behalf of John, so you’d need to ensure your handlers for POST calls against the /posts/ endpoint do not create a blog post resource while the custom header is present.  

At its core, though, this design pattern will allow you to implement secure “run as” functionality.

Thoughts? Questions? We’d love to hear them. And note that this is just one of the many suggestions, tips, and tricks you can find on our Support site. The knowledge base expands every day, so don’t hesitate to check it out, and follow @goStormpath for more best practices in the future.


Viewing all articles
Browse latest Browse all 278

Trending Articles