Feature Flagging
In this guide, we'll go over how you can implement feature flags for your application using Warrant.
What are Feature Flags?
Feature flags allow you to modify application behavior at runtime based on some specific context, without changing any code. Feature flags have a variety of application use-cases including gating feature access, A/B testing experimental features, implementing fall-back logic and safely turning off old features. Their versatility makes them 'must-have' product infrastructure.
In the remainder of this guide, we'll go over how you can use Warrant as a user-centric feature flagging system in your application.
Prerequisites
This guide exclusively uses the Warrant API and thus assumes you have a Warrant account with API keys. If you don't, please first follow the Quickstart to set up your account.
Creating a 'Feature' Object Type
There's really only 1 object type we need in order to implement feature flags: feature
. Each object of this type represents a specific feature within our application. For example, we might have a 'query_builder' feature and a 'reporting' feature.
The feature
object type only has an access
relation which allows us to grant users access to the specific feature. Putting this together, we get the following simple object type definition for feature
:
- JSON
- cURL
{
"type": "feature",
"relations": {
"access": {}
}
}
curl "https://api.warrant.dev/v1/object-types" \
-X POST \
-H "Authorization: ApiKey YOUR_KEY" \
--data-raw \
'{
"type": "feature",
"relations": {
"access": {}
}
}'
Managing Feature Access
After our feature
object type is defined, we have the ability to create warrants to manage who has access to which feature(s) in the application.
We can create these warrants adhoc in the Warrant dashboard or within our business logic using the Warrant APIs. For example, the following code can be used to grant user1
access to the 'query_builder' feature in our app:
- cURL
- Go
- Java
- Node.js
- Python
- Ruby
curl "https://api.warrant.dev/v1/warrants" \
-X POST \
-H "Authorization: ApiKey YOUR_KEY" \
--data-raw \
'{
"objectType": "feature",
"objectId": "query_builder,
"relation": "access",
"subject": {
"objectType": "user",
"objectId": "user1"
}
}'
resp, err := client.CreateWarrant(warrant.Warrant{
ObjectType: "feature",
ObjectId: "query_builder",
Relation: "access",
Subject: warrant.Subject{
ObjectType: "user",
ObjectId: "user1",
},
})
Subject warrantSubject = new Subject("user", "user1")
Warrant warrantToCreate = new Warrant("feature", "query_builder", "access", warrantSubject)
client.createWarrant(warrantToCreate);
const newWarrant = await warrantClient.Warrant.create({
object: {
objectType: "feature",
objectId: "query_builder",
},
relation: "access",
subject: {
objectType: "user",
objectId: "user1",
},
});
subject = Subject("user", "user1")
client.create_warrant(object_type="feature", object_id="query_builder", relation="access", subject=subject)
Warrant::Warrant.create(
object_type: "feature",
object_id: "query_builder",
relation: "access",
subject: {
object_type: "user",
object_id: "user1"
}
)
Checking Feature Access
With our feature flags defined, we can start to enforce access to the features in our app.
For example, in the case of the 'query_builder' feature, we will want to add the following user check in our application code that gives access to the 'query_builder' feature:
- cURL
- Go
- Java
- Node.js
- Python
- Ruby
curl "https://api.warrant.dev/v2/authorize" \
-X POST \
-H "Authorization: ApiKey YOUR_KEY" \
--data-raw \
'{
"warrants": [
{
"objectType": "feature",
"objectId": "query_builder",
"relation": "access",
"subject": {
"objectType": "user",
"objectId": USER_ID
}
}
]
}'
isAuthorized, _ := client.IsAuthorized(warrant.WarrantCheckParams{
Warrants: []warrant.Warrant{
{
ObjectType: "feature",
ObjectId: "query_builder",
Relation: "access",
Subject: warrant.Subject{
ObjectType: "user",
ObjectId: userToCheck,
},
}
}
})
if isAuthorized {
// Allow access to 'query_builder' for this user
} else {
// Fail request
}
Subject warrantSubject = new Subject("user", userToCheck);
Warrant warrantToCheck = new Warrant("feature", "query_builder", "access", warrantSubject)
boolean isAuthorized = client.isAuthorized(new WarrantCheck(Arrays.asList(warrantToCheck)));
if (isAuthorized) {
// Allow access to 'query_builder' for this user
} else {
// Fail request
}
const hasFeature = await warrantClient.Authorization.check({
warrants: [
{
object: {
objectType: "feature",
objectId: "query_builder",
},
relation: "access",
subject: {
objectType: "user",
objectId: userToCheck,
},
},
],
});
is_authorized = client.Authz.check("feature", "query_builder", "access", Subject("user", user_to_check))
if is_authorized:
# Allow access to 'query_builder' for this user
else:
# Fail request
unless Warrant::Warrant.is_authorized?(
warrants: [
{
object_type: "feature",
object_id: "query_builder",
relation: "access",
subject: {
object_type: "user",
object_id: user_to_check
}
}
])
# User Unauthorized
end
Summary
In this guide, we showed how you can use Warrant to quickly implement feature flags in your application. These feature flags exist as user-based warrants that can be modified in real-time via the Warrant dashboard and APIs. They allow us to change user access to features or application behavior at runtime without having to modify any code.