Repository demonstrating use cases and possible solutions to integrate FGA with Spring Security.
The goals of this repository are to:
- Show how OpenFGA can be integrated with Spring today
- Give insight into possible DX improvements, either through an FGA-owned starter/library, possible direct Spring Security integration, or customer guidance
simple-auth
is a sample FGA integration that has a basic Spring security configured. It is a simple example that makes assumptions about users and principals.resource-server
andclient-restclient
demonstrate a resource server with JWT authorization using theokta-spring-boot-starter
and a client credentials flow to obtain a JWT to make API calls. The API's inresource-server
are protected both by JWT and FGA checks, and are called byclient-restclient
.
- Docker
- Java 17
- OpenFGA CLI
To run the simple-auth
sample, see the README.
This sample comprises of two parts:
- A resource server configured with the
okta-spring-boot-starter
to secure endpoints with JWTs issued by Auth0. It protects APIs with JWT authorization and uses FGA to protect endpoints and write authorization data. - A client that uses the client credentials flow to obtain a JWT to call the resource server.
- Create a new Auth0 API and note the API identifier
- Create a new Auth0 machine-to-machine application, and note the client ID and secret
This will start an in-memory database OpenFGA server:
docker pull openfga/openfga:latest
docker run --rm -e OPENFGA_HTTP_ADDR=0.0.0.0:4000 -p 4000:4000 -p 8081:8081 -p 3000:3000 openfga/openfga run
Create a store:
fga store create --name "Example Store" --api-url http://localhost:4000
You should receive a response like this. Note the store ID value:
{
"store": {
"created_at":"2024-02-16T16:56:21.162910175Z",
"id":"01HPSDHYXAD9HS906YFG9CQM02",
"name":"Test Store",
"updated_at":"2024-02-16T16:56:21.162910175Z"
}
}
Create an authorization model:
fga model write --api-url http://localhost:4000 --store-id STORE-ID-FROM-ABOVE --file ./example-auth-model.json
You should receive a response like this. Note the authorization_model_id
:
{
"authorization_model_id":"01HPSDPTTC209FQ0P4AMK3AZPE"
}
Configure the application properties:
cd resource-server
cp src/main/resources/application.yml.example src/main/resources/application.yml
In application.yml
, replace the oauth2
properties with the values from your Auth0 application and API.
Also replace the values for fga-store-id
and fga-authorization-model-id
with the values created above.
./gradlew bootRun
This will start the server on port 8082.
Configure the application properties:
cd client-restclient
cp src/main/resources/application.yml.example src/main/resources/application.yml
Replace the oauth2 values and auth0-audience
with the values of your Auth0 application and API identifier.
./gradlew bootRun
This will start the application, execute the client credentials grant to obtain a JWT, and then makes calls to the resource server:
- Attempt to
GET
a "document" for which the current principal does not have an FGA relation to. This request should fail with a403
. - A call to create a "document", which will create an FGA relationship associated with the principal.
- Another attempt to get the document, which should now return successfully as there is a
reader
relationship between the principal and the document.
You can see the results of these calls in the application logs.
The samples demonstrate the following:
Uses custom application property values to create and make available to components an OpenFgaClient
. This can be used by applications to interact with the FGA API directly, e.g., to write authorization data.
An application can configure the client in application properties for their usage:
openfga.fgaApiUrl=FGA_API_URL
openfga.fgaStoreId=FGA_STORE_ID
openfga.fgaAuthorizationModelId=FGA_AUTHORIZATION_MODEL_ID
openfga.fgaApiAudience=FGA_API_AUDIENCE
openfga.fgaClientId=FGA_CLIENT_ID
openfga.fgaClientSecret=FGA_CLIENT_SECRET
...
Note that for simplicity purposes, this sample does not support FGA authorization, thus is NOT suitable for production use.
A simple bean is defined to perform an authorization check:
@PreAuthorize("@openFga.check('#id', 'document', 'reader', 'user')")
public String getDocumentWithSimpleFgaBean(@PathVariable String id) {
return "You have access!";
}
In the example above, the currently authenticated principal's name is used as the user ID by default. It can also be explicitly passed.
A custom @FgaCheck
annotation was created to demonstrate using an explicit FGA annotation and aspect to execute an FGA check prior to the method execution:
@FgaCheck(userType="user", relation="reader", objectType="document", object="#id")
public String customAnnotation(@PathVariable String id) {
return "You have access!";
}
Similar to the bean definition, it uses the currently authenticated principal by default for the user ID.