Fine Grained Authorization
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": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "owner"
}
]
},
"viewer": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "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": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "owner"
}
]
},
"viewer": {
"type": "anyOf",
"rules": [
{
"type": "userset",
"relation": "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
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",
User: warrant.WarrantUser{
UserId: "d6ed6474-784e-407e-a1ea-42a91d4c52b9",
},
})
if err != nil {
// handle error
}
try {
Warrant warrantToCreate = Warrant
.newUserWarrant("store", "7", "editor", "d6ed6474-784e-407e-a1ea-42a91d4c52b9");
client.createWarrant(warrantToCreate);
} catch (WarrantException e) {
// Handle error
}
client
.createWarrant("store", "7", "editor", {
userId: "d6ed6474-784e-407e-a1ea-42a91d4c52b9",
})
.then((newWarrant) => console.log(newWarrant))
.catch((error) => console.log(error));
try:
client.create_warrant(object_type="store", object_id="7", relation="editor", user="d6ed6474-784e-407e-a1ea-42a91d4c52b9")
except WarrantException:
# Handle error
begin
client.create_warrant("store", "7", "editor", { user:"d6ed6474-784e-407e-a1ea-42a91d4c52b9" })
rescue
# Handle error
end
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 /v1/authorize
request to Warrant for the given object type, object id, and relation a subject is attempting to access 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
curl "https://api.warrant.dev/v1/authorize" \
-X GET \
-H "Authorization: ApiKey YOUR_KEY" \
--data-raw \
'{
"objectType": "store",
"objectId": "7",
"relation": "editor",
"user": {
"objectType": "user",
"objectId": "d6ed6474-784e-407e-a1ea-42a91d4c52b9"
}
}'
isAuthorized, _ := client.IsAuthorized(warrant.Warrant{
ObjectType: "store",
ObjectId: "7",
Relation: "editor",
User: warrant.WarrantUser{
UserId: "d6ed6474-784e-407e-a1ea-42a91d4c52b9",
},
})
if !isAuthorized {
// User Unauthorized
}
if (!client.isAuthorized(Warrant.newUserWarrant("store", "7", "editor", "d6ed6474-784e-407e-a1ea-42a91d4c52b9"))) {
// User Unauthorized
}
client
.isAuthorized("store", "7", "editor", "d6ed6474-784e-407e-a1ea-42a91d4c52b9")
.then((isAuthorized) => {
if (!isAuthorized) {
// User Unauthorized
}
})
.catch((error) => console.log(error));
if not client.is_authorized(object_type="store", object_id="7", relation="editor", user_to_check="d6ed6474-784e-407e-a1ea-42a91d4c52b9"):
# User Unauthorized
unless client.is_authorized("store", "7", "editor", "d6ed6474-784e-407e-a1ea-42a91d4c52b9"):
# User Unauthorized
end
This call will return a 401 Unauthorized
if user d6ed6474-784e-407e-a1ea-42a91d4c52b9
does not have the editor
relation on store 7
either directly or indirectly through another relationship. It will return a 200 OK
if they do 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.