Fine Grained Access Control (FGAC)
Implementing an authorization scheme in Warrant involves 3 steps: (1) defining your data model (creating object types), (2) creating access rules (warrants), and (3) implementing access checks in your code. In this quickstart, we'll go through these 3 steps for implementing fine grained authorization in a simple eCommerce application.
1. Creating Object Types
Although Warrant ships with built-in object types for user, role, permission, and tenant, you may have a use case that requires your own custom object types.
For our eCommerce application, we'll create a store
object type:
{
"type": "store",
"relations": {
"owner": {},
"creator": {},
"editor": {
"inheritIf": "owner"
},
"viewer": {
"inheritIf": "editor"
}
}
}
A store
has the following direct and inherited relations:
- A store can have an owner (direct)
- A store can have a creator (direct)
- A store can have an editor (direct)
- An owner of a store is also an editor of that store (inherited)
- A store can have a viewer (direct)
- An editor of a store is also a viewer of that store (inherited)
Object types can be created directly in the dashboard or via the Object Types API:
- cURL
curl "https://api.warrant.dev/v1/object-types" \
-X POST \
-H "Authorization: ApiKey YOUR_KEY" \
--data-raw \
'{
"type": "store",
"relations": {
"owner": {},
"creator": {},
"editor": {
"inheritIf": "owner"
},
"viewer": {
"inheritIf": "editor"
}
}
}'
2. Creating Warrants
Once our object types are defined, we can create warrants that define our application's specific access control rules. Warrants are typically created from within your application business logic through the Warrants API (via REST or one of the SDKs).
We can use the built-in user
object type and our store
object type to create a warrant specifying that [user:d6ed6474-784e-407e-a1ea-42a91d4c52b9] is an [editor] of [store:7]
:
- cURL
- Go
- Java
- Node.js
- Python
- Ruby
- PHP
curl "https://api.warrant.dev/v1/warrants" \
-X POST \
-H "Authorization: ApiKey YOUR_KEY" \
--data-raw \
'{
"objectType": "store",
"objectId": "7",
"relation": "editor",
"subject": {
"objectType": "user",
"objectId": "d6ed6474-784e-407e-a1ea-42a91d4c52b9"
}
}'
resp, err := client.CreateWarrant(warrant.Warrant{
ObjectType: "store",
ObjectId: "7",
Relation: "editor",
Subject: warrant.Subject{
ObjectType: "user",
ObjectId: "d6ed6474-784e-407e-a1ea-42a91d4c52b9",
},
})
if err != nil {
// handle error
}
try {
Subject warrantSubject = new Subject("user", "d6ed6474-784e-407e-a1ea-42a91d4c52b9")
Warrant warrantToCreate = new Warrant("store", "7", "editor", warrantSubject);
client.createWarrant(warrantToCreate);
} catch (WarrantException e) {
// Handle error
}
const newWarrant = await warrantClient.Warrant.create({
object: {
objectType: "store",
objectId: "7",
},
relation: "editor",
subject: {
objectType: "user",
objectId: "d6ed6474-784e-407e-a1ea-42a91d4c52b9",
},
});
try:
subject = Subject("user", "d6ed6474-784e-407e-a1ea-42a91d4c52b9")
client.create_warrant(object_type="store", object_id="7", relation="editor", subject=subject)
except WarrantException:
# Handle error
begin
Warrant::Warrant.create(
object_type: "store",
object_id: "7",
relation: "editor",
subject: {
object_type: "user",
object_id: "d6ed6474-784e-407e-a1ea-42a91d4c52b9"
}
)
rescue
# Handle error
end
try {
$subject = new \Warrant\Subject("user", "d6ed6474-784e-407e-a1ea-42a91d4c52b9");
$client->createWarrant(new \Warrant\Warrant("store", "7", "editor", $subject));
} catch ($e) {
// Handle error
}
3. Authorizing Users
Now that we have object types, users, and warrants set up, we can start authorizing user access to our system. At minimum, we should have server-side checks but we can also implement client-side authorization using Warrant sessions and UI components.
Server-side Authorization
At a high-level, server-side authorization involves making a POST /v2/authorize
request to Warrant with the given warrants that you want to verify authorization for through each endpoint. For example, if your server implements a PUT /stores
endpoint, you may want to only allow users with the editor
relation on stores to perform that action. You can authorize a user by making a request to Warrant:
- cURL
- Go
- Java
- Node.js
- Python
- Ruby
- PHP
curl "https://api.warrant.dev/v2/authorize" \
-X GET \
-H "Authorization: ApiKey YOUR_KEY" \
--data-raw \
'{
"warrants": [
{
"objectType": "store",
"objectId": "7",
"relation": "editor",
"subject": {
"objectType": "user",
"objectId": "d6ed6474-784e-407e-a1ea-42a91d4c52b9"
}
}
]
}'
isAuthorized, _ := client.IsAuthorized(warrant.WarrantCheckParams{
Warrants: []warrant.Warrant{
{
ObjectType: "store",
ObjectId: "7",
Relation: "editor",
Subject: warrant.Subject{
ObjectType: "user",
ObjectId: "d6ed6474-784e-407e-a1ea-42a91d4c52b9",
},
}
}
})
if !isAuthorized {
// User Unauthorized
}
Subject warrantSubject = new Subject("user", "d6ed6474-784e-407e-a1ea-42a91d4c52b9")
Warrant warrantToCheck = new Warrant("store", "7", "editor", warrantSubject)
if (!client.isAuthorized(new WarrantCheck(Arrays.asList(warrantToCheck))) {
// User Unauthorized
}
const isAuthorized = await warrantClient.Authorization.check({
warrants: [
{
object: {
objectType: "store",
objectId: "7",
},
relation: "editor",
subject: {
objectType: "user",
objectId: "d6ed6474-784e-407e-a1ea-42a91d4c52b9",
},
},
],
});
if (!isAuthorized) {
// User Unauthorized
}
if not client.Authz.check("store", "7", "editor", Subject("user", "d6ed6474-784e-407e-a1ea-42a91d4c52b9")):
# User Unauthorized
unless Warrant::Warrant.check({ object_type: "store", object_id: "7" }, "editor", { object_type: "user", object_id: "d6ed6474-784e-407e-a1ea-42a91d4c52b9" })
# User Unauthorized
end
$warrants_to_check = [
new \Warrant\Warrant(
"store",
"7",
"editor",
new \Warrant\Subject("user", "d6ed6474-784e-407e-a1ea-42a91d4c52b9")
)
];
$is_authorized = $warrant->isAuthorized(new \Warrant\WarrantCheck(\Warrant\WarrantCheckOp::ALL_OF, $warrants_to_check));
if (!$is_authorized) {
// User unauthorized
}
This call will return a 200 OK
if successful. The response body will contain "result": "Not Authorized"
if user 6ed6474-784e-407e-a1ea-42a91d4c52b9
does not have the editor relation on store 7
and "result": "Authorized"
if the user does have the relation.
Client-side Authorization
If your client application is a Single Page App (SPA) or a native mobile application (iOS/Android), implementing client-side authorization using Warrant will further secure your application and help you more cleanly display different UI/UX for users with different levels of access. We currently provide SDKs for client-side authorization in applications built using React, NextJS, and VueJS. Refer to our guides on using Warrant with React, NextJS, and VueJS for a step-by-step tutorial.