Hacker News new | ask | show | jobs
by claytongulick 1582 days ago
So much of authorization is context / application dependent, I'm struggling with this a bit.

For example, I have a cluster of services. I allow access to some of them, for certain actions, based on whether the user is part of a patient's care team.

That's very dynamic, I need to do a FHIR query to one of my services to determine that. Then there's a lot more logic, like what servicer / organization affiliation the user is part of, this is also a runtime lookup in a shared session state thing, etc...

I just list all that as a basic example, there are so many things that are application specific that require runtime evaluation, it's hard for me to understand the benefit of writing all that in a different language, in a different place, where I can't use the libraries and utilities that are already part of the application.

3 comments

That's indeed why authorization is a harder problem than authentication - because much of it is domain-specific.

Still, there are many things an authorization system can help with. For example, the service/organization affiliation of the user is easily expressed as a set of attributes / properties / roles on the user. If that's stored in a central directory, and the authorizer has a cached copy, you can use this context as part of your policy for making authorization decisions.

Lifting the authorization policy into a central repo allows you to consolidate authorization logic, and also enables separation of concerns. SecOps can evolve the authorization policy of the application without having to ask developers to revisit the logic in all the places it exists. In fact, we have customers that have their secops team deploy new versions of the authorization policy without having to redeploy the application.

Another example is having front-end code that can dynamically render component state (visible or enabled) based on the same authorization rules that the back-end / API uses. We have nice examples of this in our "peoplefinder" demo [0], which you can launch as a quickstart [1].

[0] https://github.com/aserto-demo/peoplefinder

[1] https://www.aserto.com/quickstarts

But what if your services are written in different languages from each other and they need to perform similar authz checks? Sure, each service might need be responsible for its own data fetching, but the actual logic over the data can be written in one common language (Rego) and have one single source of truth.
Well, I'm in exactly this situation right now.

The approach I've taken is to implement my own application gateway using node-http-proxy.

I use npm workspaces to share common functionality between all the node parts of the application, including the api gateway / reverse proxy.

I configure the other services to trust a HTTP header that's injected in the api gateway (and explicitly deleted from incoming requests) that includes details about the user. For example, Apache Superset (and several other services) support the REMOTE_USER header.

Honestly, doing this in a high-level language like NodeJS with node-http-proxy is even simpler to do and easier to read / audit than what I've seen in Rego, plus I get to use all the common utilities for dynamic service access, database access, etc...

This borders on a "NIH" thing, but I think if you saw the actual implementation, it's even less complex than what I'm seeing from these examples. It took me a couple hours to throw together, it's easy to extend, and supports a multitude of authentication scenarios, including shared-session (which would be tough with Rego).

That approach could definitely work if all you need for authorization is context about the user. Sometimes that context does get large, and it's hard to put it all in an HTTP header. This is a common problem for SaaS products that bake a bunch of scopes into their JWT and put it into the HTTP Authorization header. We've helped some of our customers unroll that approach and create an explicit authorization service that the app calls.

Also, once you start incorporating resource-specific information in your authorization decisions, this approach starts to break down. The gateway could be made to understand resource-specific information, but then you're essentially moving the problem from the application to the gateway. And typically you want your API gateway to make forward/block decisions quickly.

Happy to chat about your use case! You can find me at @omrig / omri at aserto dot com.

This is indeed one of the benefits of the policy-as-code approach.

And Rego syntax is much easier to grok than other "-as-code" approaches (read: wall-of-yaml)

This goes straight to the core of what makes authorization in complex applications such a challenge. You hit the nail on the head when you say that authorization in inseparable from each application's unique circumstances.

At the most abstract level, an authorization decision is an answer to the question "is an identity (user, service, computer, etc.) allowed to perform an action on a resource?".

Each one of these constituents (identity, action, and resource) may include contextual information that is unique to the application at hand. Aserto gives you the ability to imbue each one with the necessary information.

Identities are stored in a directory where they can be enriched with arbitrary data. Roles, teams, attributes, and anything else that is directly tied to an identity whose actions need to be authorized.

Relevant information about the resource being accessed is collected by the application and sent as part of the authorization call.

Then there are the authorization policies that receive identity and resource information and make decisions.

There can be multiple ways to model an application's authorization scheme. In your example it sounds like the user (identity) is a care giver and the "resource" is patient data. If users belong to numerous care teams and their membership in them is highly dynamic, your application may perform the FHIR query to retrieve the identities of the the patient's care team prior to the authorization call and include that information in the resource context.

The policy can then make decision based on whether or not the acting identity is a member of the team, as well as any other relevant information (e.g. are they the patient's primary care giver or a specialist?).

There are many advantages to keeping authorization policies separate from application logic. Change management, provenance, and testability are a few.

Having a single place where all authorization policies are defined allows us to reason more deeply about the behavior of our applications.

I totally get the benefits, the part I'm struggling with is the real-world details.

Like, the amount of effort it would be to put in application logic in order to determine attributes that would then be put on to the profile would be more than that which would be required to just have auth native in the code.

Then there's the problem of keeping those attributes in sync with the application as things change.

Then there's the problem of debugging when things go wrong - instead of being able to inspect the application state and determine the issue, now I have two places and different processes for issue resolution.

I'm not talking about this in a hypothetical sense - I've tried this before. All sort of declarative rule based systems, and integration with Auth0 with attributes stored as part of the Auth0 profile, etc...

After doing it and balancing the benefits, I've found that having well-architected code base with auth as a separate concern, but embedded within the native code (as a separate class/service/module/etc... is more effective and less work with better performance characteristics.

Obviously this doesn't apply to every situation, I can think of several scenarios where third-part auth would work very well, I just don't run into them a lot.