DEV Community

Cover image for Supercharging API Security: APISIX with OpenFGA
Kaan Kahraman
Kaan Kahraman

Posted on

Supercharging API Security: APISIX with OpenFGA

Hello, fellow developers! Today, we're going to dive deep into the world of API authorization by exploring a custom plugin I've been working on: the APISIX-OpenFGA Authz Plugin. This plugin integrates OpenFGA's powerful authorization capabilities with Apache APISIX, allowing for fine-grained access control at the API gateway level.

Understanding the Need

Before we jump into the technical details, let's briefly discuss why fine-grained authorization at the API gateway level is crucial:

  • Centralized access control
  • Reduced duplication of authorization logic across microservices
  • Easier management and updating of access policies
  • Improved performance through caching and efficient checks

The Authorization Dilemma

Let's face it: as our applications grow more complex, so do our authorization needs. We're no longer in the simple world of "admin" and "user" roles. Modern apps require sophisticated, fine-grained access control that can handle intricate scenarios without breaking a sweat.

OpenFGA to the Rescue

This is where OpenFGA (Fine-Grained Authorization) comes into play. Based on Google's Zanzibar paper, OpenFGA offers a powerful authorization model that can handle even the most complex access control scenarios.

But how do we bridge the gap between APISIX and OpenFGA? That's precisely why I created the APISIX-OpenFGA Authz Plugin!

Now, let's explore how this plugin achieves these goals.

Plugin Architecture

The APISIX-OpenFGA Authz Plugin is designed to be flexible and performant. Here's an overview of its key components:

  • Resource Mapping
  • Relation Mapping
  • OpenFGA Integration
  • Caching Mechanism
  • Dynamic Configuration

Let's break down each of these components and see how they work together.

1. Resource Mapping

The plugin needs to understand how to map incoming requests to OpenFGA resources. This is done through the resource_mappings configuration. Here's an example:

luaCopyresource_mappings = {
    {
        uri_pattern = "^/api/resource/([^/]+)$",
        resource_type = "resource",
        id_location = "last_part"
    }
}
Enter fullscreen mode Exit fullscreen mode

In this configuration:

  • uri_pattern is a regular expression that matches the incoming request URI
  • resource_type specifies the type of resource in OpenFGA
  • id_location tells the plugin where to find the resource ID (in this case, the last part of the URI)

The plugin uses this configuration to extract the resource type and ID from the incoming request. For example, a request to /api/resource/123would be mapped to a resource of type "resource" with ID "123".

2. Relation Mapping

Different HTTP methods often correspond to different permissions. The relation_mappings configuration allows you to map HTTP methods to OpenFGA relations:

relation_mappings = {
    GET = "reader",
    POST = "writer",
    PUT = "writer",
    DELETE = "admin"
}
Enter fullscreen mode Exit fullscreen mode

This configuration maps GET requests to the "reader" relation, POST and PUT to "writer", and DELETE to "admin".

3. OpenFGA Integration

The core of the plugin is its integration with OpenFGA. When a request comes in, the plugin constructs an OpenFGA check request based on the extracted resource information and the mapped relation. Here's a simplified version of how this works:

local res, err = client:request_uri(conf.openfga_url .. "/stores/" .. conf.store_id .. "/check", {
    method = "POST",
    headers = {
        ["Content-Type"] = "application/json",
    },
    body = json.encode({
        tuple_key = {
            user = "user:" .. user_id,
            relation = relation,
            object = resource_type .. ":" .. resource_id,
        },
        authorization_model_id = conf.authorization_model_id,
    }),
})
Enter fullscreen mode Exit fullscreen mode

This sends a check request to OpenFGA and receives a response indicating whether the action is allowed or not.

4. Caching Mechanism

To improve performance, the plugin implements a caching mechanism using APISIX's built-in LRU cache:

local allowed, err = lrucache(cache_key, nil, function()
    -- OpenFGA check logic here
end)
Enter fullscreen mode Exit fullscreen mode

This caches the authorization decision for a configurable period, reducing the number of requests to OpenFGA and improving response times.

5. Dynamic Configuration

The plugin supports dynamic configuration updates without requiring an APISIX restart. This is achieved through a shared dictionary and an API endpoint:

function _M.api()
    return {
        {
            methods = { "POST" },
            uri = "/apisix/plugin/openfga_authz/config",
            handler = function(conf, ctx)
                local req_body = core.request.get_body()
                local config = json.decode(req_body)
                update_dynamic_config("relation_mappings", config.relation_mappings)
                return 200, { message = "Configuration updated successfully" }
            end
        }
    }
end
Enter fullscreen mode Exit fullscreen mode

This allows you to update certain configurations (like relation mappings) on the fly.

Handling Different URIs

The plugin's flexibility in handling different URIs comes from its resource mapping configuration. Let's look at a few examples:

  1. Simple resource:
{
    uri_pattern = "^/api/documents/([^/]+)$",
    resource_type = "document",
    id_location = "last_part"
}
Enter fullscreen mode Exit fullscreen mode

This would match URIs like /api/documents/123.

  1. Nested resources:
{
    uri_pattern = "^/api/folders/([^/]+)/documents/([^/]+)$",
    resource_type = "document",
    id_location = "last_part"
}
Enter fullscreen mode Exit fullscreen mode

This would match URIs like /api/folders/abc/documents/123.

  1. Query parameter:
{
    uri_pattern = "^/api/search$",
    resource_type = "search",
    id_location = "query_param",
    id_key = "q"
}
Enter fullscreen mode Exit fullscreen mode

This would match URIs like /api/search?q=hello.

By configuring multiple resource mappings, you can handle a wide variety of URI patterns in your API.

Possibilities and Use Cases

The flexibility of this plugin opens up many possibilities:

  1. Microservices Architecture: Implement consistent authorization across all your microservices without duplicating logic.
  2. Multi-tenancy: Easily manage access control in multi-tenant applications by incorporating tenant information into your OpenFGA model.
  3. Dynamic Permissions: Update access control policies on the fly without redeploying your application.
  4. Audit Logging: Since all authorization decisions go through the plugin, it's an ideal place to implement comprehensive audit logging.
  5. A/B Testing of Authorization Policies: Use the dynamic configuration feature to test new authorization policies on a subset of requests.

Inspiration

This plugin was heavily inspired by works of Martini Besozzi if you would like to dive deeper into the world of authentication and authorization, check his amazing Medium Articles:

  1. Mastering Access Control: Low-Code Authorization with ReBAC, Decoupling Patterns and Policy as Code
  2. Keycloak integration with OpenFGA (based on Zanzibar) for Fine-Grained Authorization at Scale (ReBAC)

Conclusion

The APISIX-OpenFGA Authz Plugin provides a powerful way to implement fine-grained authorization in your API gateway. By understanding its components and configuration options, you can tailor it to fit a wide variety of authorization scenarios.

Remember, effective authorization is about more than just implementing a plugin, it requires careful thought about your authorization model and how it maps to your business rules. OpenFGA provides a flexible foundation for expressing these rules, and this plugin brings that power to your API gateway.

I hope this deep dive has given you a better understanding of how fine-grained authorization can be implemented at the API gateway level. Feel free to explore the full source code and experiment with different configurations to see how it can fit into your architecture. Any feedback and suggestions are more than welcome!

Happy coding, and here's to building more secure and flexible APIs!

Top comments (0)