Skip to content

Commit 769e67a

Browse files
committed
feat: Add an example of access control at the API Gateway level with APISIX and the plugin authz-openfga, based on the article Mastering Access Control: Implementing Low-Code Authorization Based on ReBAC and the Decoupling Pattern.
1 parent 5e38783 commit 769e67a

17 files changed

+2257
-6
lines changed

.env

+4
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
KC_VERSION=25.0.1
22
OPENFGA_VERSION=v1.5.5
3+
4+
AM_INTERNAL_URL=http://keycloak:8080
5+
OPENFGA_HOST=http://openfga:8080
6+
STORE_API=host.docker.internal:8091

README.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ In this new version of the PoC we have a direct integration between the Access M
55

66
This workshop is based the following article [Keycloak integration with OpenFGA (based on Zanzibar) for Fine-Grained Authorization at Scale (ReBAC)](https://embesozzi.medium.com/keycloak-integration-with-openfga-based-on-zanzibar-for-fine-grained-authorization-at-scale-d3376de00f9a). You will find there full details about the authorization architecture guidelines and involved components.
77

8+
In the latest version, I aimed to continue improving the authorization architecture and provide an agnostic approach for exposing and protecting APIs following best practices such as Policy as Code (PaC), decoupling authorization, and low-code authorization. Therefore, I added an identity-aware API gateway, Apache APISIX, as an API sidecar to the authorization architecture to enforce authorization and decouple it from the backend. The gateway uses a plugin I developed called [authz-openfga](https://github.com/embesozzi/apisix-authz-openfga) that supports Relationship-Based Access Control (ReBAC) policies because it's integrated with OpenFGA platform. The details are explained in the following article [Mastering Access Control: Implementing Low-Code Authorization Based on ReBAC and Decoupling Pattern](https://medium.com/@embesozzi/mastering-access-control-implementing-low-code-authorization-based-on-rebac-and-decoupling-pattern-f6f54f70115e)
89

910
## Authorization Framework (New)
1011

@@ -23,7 +24,10 @@ The following diagram illustrates the solution architecture of this workshop:
2324
* OpenFGA is responsible for applying fine-grained access control. The OpenFGA service answers authorization checks by determining whether a relationship exists between an object and a user.
2425
* Other components
2526
* Store Web Application is integrated with Keycloak by OpenID Connect
26-
* Store API is protected by OAuth 2.0 and it utilizes the OpenFGA SDK for FGA
27+
* Store Authorization Gateway exposes and protects the Store API with ReBAC policies integrated with the OpenFGA Platform (Protection at API Gateway).
28+
29+
You can also have Store OpenFGA API is protected by OAuth 2.0 and it utilizes the OpenFGA SDK for FGA as example (Protection at API Level).
30+
2731

2832
Another cool feature of custom extension is its capability to discover the OpenFGA authorization model and determine which events are handled. This gives you the flexibility to choose your authorization model, whether it’s RBAC, GBAC, or both 🙌.
2933

@@ -49,7 +53,7 @@ Another cool feature of custom extension is its capability to discover the OpenF
4953
3. To be able to use this environment, you need to add this line to your local HOSTS file:
5054

5155
```sh
52-
127.0.0.1 keycloak openfga store store-api
56+
127.0.0.1 keycloak openfga store store-openfga-api store-authz-gateway
5357
```
5458

5559
4. Access the following web UIs using URLs bellow via a web browser.
@@ -59,7 +63,8 @@ Another cool feature of custom extension is its capability to discover the OpenF
5963
| Keycloak Console | http://keycloak:8081 | admin / password | quay.io/keycloak/keycloak:25.0.1 |
6064
| OpenFGA Playground | http://localhost:3000/playground | | openfga/openfga:v1.5.5 |
6165
| Store Portal | http://store:9090 | | Custom image |
62-
| Store API | http://store-api:9091 | | Custom image |
66+
| Store Authorization Gateway | http://store-authz-gateway:9080 | | Custom image based Apache APISIX Gateway |
67+
| Store OpenFGA API | http://store-openfga:9091 | | Custom image |
6368

6469

6570

@@ -108,6 +113,8 @@ As an example, we will implement an Product Catalog web application that has the
108113

109114
You can follow the test cases described in the [Keycloak integration with OpenFGA (based on Zanzibar) for Fine-Grained Authorization at Scale (ReBAC)](https://embesozzi.medium.com/keycloak-integration-with-openfga-based-on-zanzibar-for-fine-grained-authorization-at-scale-d3376de00f9a).
110115

116+
And the article [Mastering Access Control: Implementing Low-Code Authorization Based on ReBAC and Decoupling Pattern](https://medium.com/@embesozzi/mastering-access-control-implementing-low-code-authorization-based-on-rebac-and-decoupling-pattern-f6f54f70115e).
117+
111118
Nevertheless, the use cases are detailed below:
112119

113120
### Use case 1: Access to the Store for managing products as an Analyst (Paula)

docker-compose-apps.yml

+29-3
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ services:
99
- "9090:8080"
1010
environment:
1111
VUE_APP_OIDC_PROVIDER_DOMAIN: http://keycloak:8081/realms/master
12-
VUE_APP_API_URL: http://store-api:9091/api/products
12+
# VUE_APP_API_URL: http://store-openfga-api:9091/api/products
13+
VUE_APP_API_URL: http://store-authz-gateway:9080/api/products
1314
VUE_APP_CLIENT_ID: portal
1415
depends_on:
1516
keycloak:
1617
condition: service_healthy
1718

18-
store-api:
19+
store-openfga-api:
1920
build: ./store-openfga-api
2021
image: twogenidentity/store-openfga-api
2122
container_name: store-openfga-api
@@ -25,4 +26,29 @@ services:
2526
ports:
2627
- "9091:9091"
2728
environment:
28-
OIDC_PROVIDER_DOMAIN: http://keycloak:8081/realms/master
29+
OIDC_PROVIDER_DOMAIN: http://keycloak:8081/realms/master
30+
31+
store-authz-gateway:
32+
image: ghcr.io/embesozzi/apisix-authz-openfga:latest
33+
container_name: store-authz-gateway
34+
depends_on:
35+
keycloak:
36+
condition: service_healthy
37+
volumes:
38+
- $PWD/store-authz-gateway/routes.yml:/usr/local/apisix/conf/apisix.yaml:ro
39+
- $PWD/store-authz-gateway/config.yml:/usr/local/apisix/conf/config.yaml:ro
40+
environment:
41+
KEYCLOAK_URL: ${AM_INTERNAL_URL}
42+
KEYCLOAK_CLIENT_SECRET: jnxDqhu0GTaCCWuKxodUnSdKzEIBquKT
43+
SESSION_SECRET: nFpaoPPxHGHJLFlE12qx8P5TGgJBDYdS
44+
FGA_HOST: ${OPENFGA_HOST}
45+
STORE_API: ${STORE_API}
46+
ports:
47+
- "9080:9080"
48+
49+
store-api:
50+
build: ./store-api
51+
image: ghcr.io/embesozzi/demo-store-api:1.0.0
52+
container_name: store-api
53+
# ports:
54+
# - "7091:7091"

keycloak/import.sh

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ echo "Creating PoC Users, Role Model, User Role Assigments and Clients"
99

1010
# Clients
1111
/opt/keycloak/bin/kcadm.sh create clients -r master -s clientId=portal -s publicClient=true -s 'redirectUris=["http://store:9090/callback"]' -s 'webOrigins=["http://store:9090"]' -s 'attributes={ "post.logout.redirect.uris": "http://store:9090/home?action=logout", "access.token.lifespan": 3600}' -o
12+
/opt/keycloak/bin/kcadm.sh create clients -r master -s clientId=apisix -s 'redirectUris=["http://localhost:9980/callback"]' -s 'secret=jnxDqhu0GTaCCWuKxodUnSdKzEIBquKT' -o
1213

1314
# Users
1415
/opt/keycloak/bin/kcadm.sh create users -r master -s username=paula -s firstName=Paula -s lastName=Von -s enabled=true -s [email protected]

store-api/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.DS_Store

store-api/Dockerfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM node:16.0.0
2+
# FROM --platform=linux/amd64 node:16.0.0
3+
LABEL maintainer="[email protected]"
4+
5+
WORKDIR /app
6+
COPY package*.json ./
7+
RUN npm install
8+
COPY . .
9+
10+
CMD [ "npm", "start"]

store-api/openapi.yml

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
openapi: 3.0.1
2+
info:
3+
title: Products API
4+
description: 'API Products (secured using OAuth) protected by ReBAC (Zanzibar)'
5+
version: 1.0.0
6+
servers:
7+
- url: http://localhost:9980/api/products
8+
tags:
9+
- name: Product
10+
description: Operations about identity and access platform
11+
paths:
12+
/products:
13+
get:
14+
tags:
15+
- Product
16+
summary: Get products
17+
security: []
18+
x-apisix-plugins:
19+
authz-rebac:
20+
object_type: role
21+
object: products-view
22+
responses:
23+
401:
24+
$ref: '#/components/responses/UnauthorizedError'
25+
200:
26+
description: successful operation
27+
content:
28+
application/json:
29+
schema:
30+
type: array
31+
items:
32+
$ref: '#/components/schemas/Product'
33+
/product:
34+
post:
35+
tags:
36+
- Product
37+
summary: "Add a new product"
38+
security: []
39+
x-apisix-plugins:
40+
authz-rebac:
41+
object_type: role
42+
object: products-editor
43+
requestBody:
44+
description: Information about a new user in the system
45+
content:
46+
application/json:
47+
schema:
48+
$ref: "#/components/schemas/Product"
49+
responses:
50+
"405":
51+
description: "Invalid input"
52+
"201":
53+
description: Created
54+
put:
55+
tags:
56+
- Product
57+
summary: Update an existing product
58+
description: Update an existing product by Id
59+
x-apisix-plugins:
60+
authz-rebac:
61+
object_type: role
62+
object: products-editor
63+
requestBody:
64+
description: Update an existent product in the store
65+
content:
66+
application/json:
67+
schema:
68+
$ref: '#/components/schemas/Product'
69+
required: true
70+
responses:
71+
'200':
72+
description: Successful operation
73+
content:
74+
application/json:
75+
schema:
76+
$ref: '#/components/schemas/Product'
77+
'400':
78+
description: Invalid ID supplied
79+
'404':
80+
description: Product not found
81+
'/product/{product_id}':
82+
delete:
83+
tags:
84+
- Product
85+
summary: "Delete a product"
86+
x-apisix-plugins:
87+
authz-rebac:
88+
object_type: role
89+
object: products-editor
90+
responses:
91+
"200":
92+
description: Created
93+
parameters:
94+
- schema:
95+
type: integer
96+
name: product_id
97+
in: path
98+
required: true
99+
components:
100+
responses:
101+
UnauthorizedError:
102+
description: Access token is missing or invalid
103+
schemas:
104+
Product:
105+
type: object
106+
properties:
107+
id:
108+
type: integer
109+
format: int64
110+
name:
111+
type: string
112+
xml:
113+
name: User
114+
securitySchemes:
115+
bearerOAuth:
116+
type: http
117+
scheme: bearer
118+
bearerFormat: JWT

0 commit comments

Comments
 (0)