As a financial institution, we take authorization—deciding who is allowed to do what—extremely seriously. But you don’t need an authorization framework to build an application with robust security and access control. In fact, the increased complexity and indirection that authorization frameworks require can actually make your software less secure.
At Betterment, we follow key principles to avoid authorization frameworks altogether in many of our applications. Of course, it would be impractical to completely avoid authorization features in internal applications that support our team’s diverse responsibilities.
For these apps, Betterment reframed the problem and built a radically simpler authorization framework by following a few simple ground rules.
The Downside of Frameworks
Application security is tough to get right. Some problems, like cryptography, are so thorny that even implementing a well-known algorithm yourself would be malpractice.
At Betterment, we’ve found that authorization rules emerge naturally from our business logic, and we believe that’s where they belong.
Most authorization frameworks blur the lines around this crucial piece of a business domain, leaving engineers to wonder whether and how to leverage the authorization framework versus treating a given condition as a regular business rule.
Over time, these hundreds or thousands of successive decisions can result in a minefield of inconsistent, unauditable semantics, and ultimately the confusion can lead to bugs and vulnerabilities.
Betterment has structured our entire platform around the security of our customers. By following the principles in this article, we’ve simplified the authorization problem, making decisions easy and accountable, and achieving even higher confidence in our systems’ safety.
Authorization Without the Framework
Here are the principles that keep Betterment’s most security-critical apps free of authorization frameworks:
Authorization Through Impossibility
The most fundamental authorization rule of an app like Betterment’s is that users should only be able to see their own financial profiles.
That could be modeled in an authorization framework by specifying that users only have access to their own profiles, and then querying the framework before display.
But there’s a better way: Make it impossible. We simply don’t have an endpoint to allow somebody to request another user’s information. The most secure code is the code that never got written.
Authorization Through Navigability
Most things that could be described as authorization rules emerge naturally from relationships.
For instance, if I’m co-owner of a joint account opened by my spouse, then I am allowed to see that account. Rather than add another layer of indirection, we simply rely on our data model, and only expose data that can be reached through the app’s natural relationships. The app can’t even locate data that should be inaccessible.
Authorization Through Application Boundaries
Many arguments for heavyweight authorization arise from administrative access. What if a Customer Support representative needs the ability to help a customer make a change to an account? Shouldn’t there be a simple override available to her?
No, at least not within the same app. Each application should have a single audience.
For instance, our consumer-facing app lets customers view and manage their investments. Our internal Customer Support app allows our Customer Support representatives to look up the accounts of customers they’re assisting.
Our Ops app gives our broker-dealer operations team the tools to monitor risk systems and manage transactions. This isn’t just a boon for security—it’s better software.
Each app is built for a specific team with exactly the tools and information it needs.
But Sometimes You Need a Framework
The real world is complicated. At Betterment, where we’re approaching 200 employees across many disciplines, we know this well.
Some tasks require a senior team member. Some trainees only need limited access to a system.
As an engineering organization, you could build a new app for every single title and level within your company, but it’d be confusing for team members whose jobs are more similar than they are different, and mind-bogglingly expensive to maintain. How do you move forward without going whole-hog on heavyweight authorization?
By setting a few ground rules for ourselves, we were able to design a lightweight, auditable, and intuitive approach to authorization that has scaled with our team and stayed dead simple. Here were the rules we followed:
1. Privilege Levels Are Named After the People Who Use the Software
As Phil Karlton once said, naming things is one of the two hard things in computer science. Software is built by people, for people. To tend toward security over the long term, the names we use must be intuitive to both the engineers building the software and its users.
For instance, our Customer Support app has the levels
manager. As the organization grows and matures, internal jargon will change too. It’s crucial to make sure these names remain meaningful, updating them as needed.
2. Privilege Levels Are Linear
Once you’ve built a separate app for each audience, you don’t need to support multiple orthogonal roles—a single ladder is enough. In our Customer Support app,
staff can do a superset of what
trainees can do, and
managers can do a superset of what
staff can do.
In combination with the naming rule, this means that you can easily add levels above, below, or between the existing levels as your team grows without rethinking every privilege in the system.
Eventually, you may find that a single ladder isn’t enough. This is a great opportunity to force the conversation about how roles within your team are diverging, and build software to match.
3. REST Resources Are the Only Resources, and HTTP Verbs Are the Only Actions
At their core, all authorization systems determine whether a user has permission to perform an action on a resource.
Much of the complexity in a traditional authorization system comes from defining those resources and their relationships to users, usually in terms of database entities. RESTful applications have the concepts of resources and actions in their DNA, so there’s no need to reinvent that wheel.
REST doesn’t just give us the ability to define simple resources like
accounts that correspond to database tables. We can build up semantic concepts like a
search resource to enable basic user lookup, and a
secure_search resource that allows senior team members to query by sensitive details like Social Security number.
By treating HTTP verbs as our actions, we can easily allow a
GET an account but not
4. Authorization Framework Calls Are Simple, and Stay in the Controllers and Views
If the only way to initiate an action is through a REST endpoint, there’s no need to add complexity to your business logic layer. The authorization framework only has two features:
- To answer whether a user can request a given resource with a given verb. App developers use this feature to customize views (e.g., show or hide a button).
- To abort requests for a resource if the answer is no.
And that’s all you need.
Help Us Solve the Hard Problems
Security is a mindset, philosophy, and practice more than a set of tools or solutions, and many challenges lie ahead. If you’d like to help Betterment design, build, and spread radically simpler and more secure solutions to the hard problems our customers and team face, apply here.
More from Betterment:
- The Evolution of the Betterment Engineering Interview
- Engineering at Betterment: Do You Have to Be a Financial Expert?