Perform a List Users call
ListUsers is currently in an experimental release. Read the announcement for more information.
This section will illustrate how to perform a list users request to determine all the users of a given type that have a specified relationship with a given object.
Before You Start
- Node.js
- Go
- .NET
- Java
- CLI
- curl
- Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA_STORE_ID, FGA_API_URL and, if needed, FGA_API_TOKEN.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_STORE_ID
andFGA_API_URL
as environment variables.
- Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA_STORE_ID, FGA_API_URL and, if needed, FGA_API_TOKEN.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_STORE_ID
andFGA_API_URL
as environment variables.
- Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA_STORE_ID, FGA_API_URL and, if needed, FGA_API_TOKEN.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_STORE_ID
andFGA_API_URL
as environment variables.
- Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA_STORE_ID, FGA_API_URL and, if needed, FGA_API_TOKEN.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_STORE_ID
andFGA_API_URL
as environment variables.
- Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA_STORE_ID, FGA_API_URL and, if needed, FGA_API_TOKEN.
- You have configured the authorization model.
- You have loaded
FGA_STORE_ID
andFGA_API_URL
as environment variables.
- Deploy an instance of the OpenFGA server, and have ready the values for your setup: FGA_STORE_ID, FGA_API_URL and, if needed, FGA_API_TOKEN.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_STORE_ID
andFGA_API_URL
as environment variables.
Step By Step
Assume that you want to list all users of type user
that have a reader
relationship with document:planning
:
01. Configure the OpenFGA API Client
Before calling the ListUsers API, you will need to configure the API client.
- Node.js
- Go
- .NET
- Java
- CLI
- curl
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});
import (
"os"
. "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
)
func main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example
StoreId: os.Getenv("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Optional, can be overridden per request
})
if err != nil {
// .. Handle error
}
}
// import the SDK
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
class Example {
public static async Task Main() {
// Initialize the SDK with no auth - see "How to setup SDK client" for more options
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for `CreateStore` and `ListStores`, required before calling for all other methods
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request
};
var fgaClient = new OpenFgaClient(configuration);
}
}
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"
.storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()
.authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request
var fgaClient = new OpenFgaClient(config);
}
}
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
To obtain the access token:
Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)
02. Calling List Users API
To return all users of type user
that have have the reader
relationship with document:planning
:
- Node.js
- Go
- .NET
- Java
- CLI
- curl
const response = await fgaClient.listUsers({
object: {
type: "document",
id: "planning"
},
user_filters: [{
type: "user",
}],
relation: "reader",
}, {
authorization_model_id: "01HVMMBCMGZNT3SED4Z17ECXCA",
});
// response.users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
// response.excluded_users = []
options := ClientListUsersOptions{
AuthorizationModelId: PtrString("01HVMMBCMGZNT3SED4Z17ECXCA"),
}
userFilters := []openfga.UserTypeFilter{{ Type:"user" }}
body := ClientListUsersRequest{
Object: openfga.Object{
Type: "document",
Id: "planning",
},
Relation: "reader",
UserFilters: userFilters,
}
data, err := fgaClient.ListUsers(context.Background()).
Body(body).
Options(options).
Execute()
// data.Users = [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}]
// data.ExcludedUsers = []
var options = new ClientWriteOptions {
AuthorizationModelId = "01HVMMBCMGZNT3SED4Z17ECXCA",
};
var body = new ClientListUsersRequest {
Object = new FgaObject {
Type = "document",
Id = "planning"
},
Relation = "reader",
UserFilters = new List<UserTypeFilter> {
new() {
Type = "user"
}
}
};
var response = await fgaClient.ListUsers(body, options);
// response.Users = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
// response.ExcludedUsers = []
var options = new ClientListUsersOptions()
.authorizationModelId("01HVMMBCMGZNT3SED4Z17ECXCA");
var userFilters = new ArrayList<UserTypeFilter>() {
{
add(new UserTypeFilter().type("user"));
}
};
var body = new ClientListUsersRequest()
._object(new FgaObject().type("document").id("planning"))
.relation("reader")
.userFilters(userFilters);
var response = fgaClient.listUsers(body, options).get();
// response.getUsers() = [{"object":{"type":"user","id":"anne"}},{"object":{"type":"user","id":"beth"}}]
// response.getExcludedUsers() = []
fga query list-users --store-id=${FGA_STORE_ID} --model-id=01HVMMBCMGZNT3SED4Z17ECXCA --object document:planning --relation reader --user-filter user
# Response: {"users": [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}],"excluded_users":[]}
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-users \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HVMMBCMGZNT3SED4Z17ECXCA",
"object": {
"type: "document",
"id": "planning",
},
"relation": "reader",
"user_filters": [
{
"type": "user"
}
]
}'
# Response: {"users": [{"object":{"type":"user","id":"anne"}}, {"object":{"type":"user","id":"beth"}}],"excluded_users":[]}
The result user:anne
and user:beth
are the user
objects that have the reader
relationship with document:planning
.
The performance characteristics of the ListUsers endpoint vary drastically depending on the model complexity, number of tuples, and the relations it needs to evaluate. Relations with 'and' or 'but not' are particularly expensive to evaluate.
Usersets
In the above example, only specific subjects of the user
type were returned. However, groups of users, known as usersets, can also be returned from the List Users API. This is done by specifying a relation
field in the user_filters
request object. Usersets will only expand to the underlying subjects if that type
is specified as the user filter object.
Below is an example where usersets can be returned:
model
schema 1.1
type user
type group
relations
define member: [ user ]
type document
relations
define viewer: [ group#member ]
With the tuples:
user | relation | object |
---|---|---|
group:engineering#member | viewer | document:1 |
group:product#member | viewer | document:1 |
user:will | member | group:engineering#member |
Then calling the List Users API for document:1
with relation viewer
of type group#member
will yield the below response. Note that the user:will
is not returned, despite being a member of group:engineering#member
because the user_filters
does not target the user
type.
- Node.js
- Go
- .NET
- Java
- CLI
- curl
const response = await fgaClient.listUsers({
object: {
type: "document",
id: "1"
},
user_filters: [{
type: "group#member",
}],
relation: "viewer",
}, {
authorization_model_id: "01HXHK5D1Z6SCG1SV7M3BVZVCV",
});
// response.users = [{"userset":{"id":"engineering","relation":"member","type":"group"}},{"userset":{"id":"product","relation":"member","type":"group"}}]
// response.excluded_users = []
options := ClientListUsersOptions{
AuthorizationModelId: PtrString("01HXHK5D1Z6SCG1SV7M3BVZVCV"),
}
userFilters := []openfga.UserTypeFilter{{ Type:"group#member" }}
body := ClientListUsersRequest{
Object: openfga.Object{
Type: "document",
Id: "1",
},
Relation: "viewer",
UserFilters: userFilters,
}
data, err := fgaClient.ListUsers(context.Background()).
Body(body).
Options(options).
Execute()
// data.Users = [{"userset":{"id":"engineering","relation":"member","type":"group"}}, {"userset":{"id":"product","relation":"member","type":"group"}}]
// data.ExcludedUsers = []
var options = new ClientWriteOptions {
AuthorizationModelId = "01HXHK5D1Z6SCG1SV7M3BVZVCV",
};
var body = new ClientListUsersRequest {
Object = new FgaObject {
Type = "document",
Id = "1"
},
Relation = "viewer",
UserFilters = new List<UserTypeFilter> {
new() {
Type = "group#member"
}
}
};
var response = await fgaClient.ListUsers(body, options);
// response.Users = [{"userset":{"id":"engineering","relation":"member","type":"group"}},{"userset":{"id":"product","relation":"member","type":"group"}}]
// response.ExcludedUsers = []
var options = new ClientListUsersOptions()
.authorizationModelId("01HXHK5D1Z6SCG1SV7M3BVZVCV");
var userFilters = new ArrayList<UserTypeFilter>() {
{
add(new UserTypeFilter().type("group#member"));
}
};
var body = new ClientListUsersRequest()
._object(new FgaObject().type("document").id("1"))
.relation("viewer")
.userFilters(userFilters);
var response = fgaClient.listUsers(body, options).get();
// response.getUsers() = [{"userset":{"id":"engineering","relation":"member","type":"group"}},{"userset":{"id":"product","relation":"member","type":"group"}}]
// response.getExcludedUsers() = []
fga query list-users --store-id=${FGA_STORE_ID} --model-id=01HXHK5D1Z6SCG1SV7M3BVZVCV --object document:1 --relation viewer --user-filter group#member
# Response: {"users": [{"userset":{"id":"engineering","relation":"member","type":"group"}}, {"userset":{"id":"product","relation":"member","type":"group"}}],"excluded_users":[]}
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-users \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HXHK5D1Z6SCG1SV7M3BVZVCV",
"object": {
"type: "document",
"id": "1",
},
"relation": "viewer",
"user_filters": [
{
"type": "group#member"
}
]
}'
# Response: {"users": [{"userset":{"id":"engineering","relation":"member","type":"group"}}, {"userset":{"id":"product","relation":"member","type":"group"}}],"excluded_users":[]}
Type-bound Public Access
The list users API supports tuples expressing public access via the wildcard syntax (e.g. user:*
). Wildcard tuples that satisfy the query criteria will be returned with the wildcard
root object property that will specify the type. The API will not expand wildcard results further to any ID'd subjects. Further, specific users that have been granted accesss will be returned in addition to any public acccess for that user's type.
Example response with type-bound public access:
{
"users": [
{
"wildcard": {
"type":"user"
}
},
{
"object": {
"type":"user",
"id":"anne"
}
}
],
"excluded_users":[]
}
Excluded Users
In certain cases, it is important to communicate that certain users are excluded from returned usersets and do not have a relation to the target obect. Most notably, this occurs in models with type-bound public access via wildcard syntax (e.g. user:*
) and negation via the but not
syntax.
Below is an example where excluded users are returned:
model
schema 1.1
type user
type document
relations
define viewer: [user:*] but not blocked
define blocked: [user]
With the tuples:
user | relation | object |
---|---|---|
user:* | viewer | document:1 |
user:anne | blocked | document:1 |
Calling the List Users API for document:1
with relation viewer
of type user
will yield the response below. It indicates that any object of type user
(including those not already in OpenFGA as parts of tuples) has access to the system, except for a user
with id anne
.
- Node.js
- Go
- .NET
- Java
- CLI
- curl
const response = await fgaClient.listUsers({
object: {
type: "document",
id: "1"
},
user_filters: [{
type: "user",
}],
relation: "viewer",
}, {
authorization_model_id: "01HXHKQX73VA6MJ3SRSY0D05VW",
});
// response.users = [{"wildcard":{"type":"user"}}]
// response.excluded_users = [{"object":{"type":"user","id":"anne"}}]
options := ClientListUsersOptions{
AuthorizationModelId: PtrString("01HXHKQX73VA6MJ3SRSY0D05VW"),
}
userFilters := []openfga.UserTypeFilter{{ Type:"user" }}
body := ClientListUsersRequest{
Object: openfga.Object{
Type: "document",
Id: "1",
},
Relation: "viewer",
UserFilters: userFilters,
}
data, err := fgaClient.ListUsers(context.Background()).
Body(body).
Options(options).
Execute()
// data.Users = [{"wildcard":{"type":"user"}}]
// data.ExcludedUsers = [{"object":{"type":"user","id":"anne"}}]
var options = new ClientWriteOptions {
AuthorizationModelId = "01HXHKQX73VA6MJ3SRSY0D05VW",
};
var body = new ClientListUsersRequest {
Object = new FgaObject {
Type = "document",
Id = "1"
},
Relation = "viewer",
UserFilters = new List<UserTypeFilter> {
new() {
Type = "user"
}
}
};
var response = await fgaClient.ListUsers(body, options);
// response.Users = [{"wildcard":{"type":"user"}}]
// response.ExcludedUsers = [{"object":{"type":"user","id":"anne"}}]
var options = new ClientListUsersOptions()
.authorizationModelId("01HXHKQX73VA6MJ3SRSY0D05VW");
var userFilters = new ArrayList<UserTypeFilter>() {
{
add(new UserTypeFilter().type("user"));
}
};
var body = new ClientListUsersRequest()
._object(new FgaObject().type("document").id("1"))
.relation("viewer")
.userFilters(userFilters);
var response = fgaClient.listUsers(body, options).get();
// response.getUsers() = [{"wildcard":{"type":"user"}}]
// response.getExcludedUsers() = [{"object":{"type":"user","id":"anne"}}]
fga query list-users --store-id=${FGA_STORE_ID} --model-id=01HXHKQX73VA6MJ3SRSY0D05VW --object document:1 --relation viewer --user-filter user
# Response: {"users": [{"wildcard":{"type":"user"}}],"excluded_users":[{"object":{"type":"user","id":"anne"}}]}
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/list-users \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{
"authorization_model_id": "01HXHKQX73VA6MJ3SRSY0D05VW",
"object": {
"type: "document",
"id": "1",
},
"relation": "viewer",
"user_filters": [
{
"type": "user"
}
]
}'
# Response: {"users": [{"wildcard":{"type":"user"}}],"excluded_users":[{"object":{"type":"user","id":"anne"}}]}