Migrating Models To Schema 1.1
Please note that at this point in time, it is not considered production-ready and does not come with any SLAs; availability and uptime are not guaranteed. Limitations of Auth0 FGA during the Developer Community Preview can be found here.
The Auth0 FGA model schema v1.0 is being deprecated. Read about when support will be dropped in the deprecation timeline.
A new DSL schema version has been introduced with several changes that we
believe will make models easier to read and write, enable better tuple and model validations, and provide more options for
optimizing the performance of different Auth0 FGA APIs.
In short, we will be:
- Adding model schema version field
- Adding type restrictions and removing need to specify
as self
- Disallowing string literals in user_ids
- Enforcing type restrictions
- Requiring you to specify for which relations you can write tuples with public access
- Changes in query evaluation behavior with type restrictions
To facilitate migration to the new DSL schema, you will need to update tuples that are no longer valid. In particular, all tuples whose user
field involves a wildcard character (*
or user:*
) defined with model schema 1.0 MUST be deleted and re-added back.
Before starting to migrate to the new model schema, it is recommended that you obtain your current authorization model ID and ensure that all your check, write, expand and list object are performed against that model id. This allows consistent behavior in your production system until you are ready to switch to the new model.
Auth0 FGA Model Schema Versions
Since the changes in the DSL are significant, we have decided to add a schema version to the DSL. The previous version of the DSL’s schema was 1.0
, and the new schema version will be 1.1
. To use the new syntax please add the following to the top of the model:
model
schema 1.1
Type Restrictions & Removing as self
We’ll use the following version 1.0 model and tuples to illustrate the changes we’ll need to make:
- DSL
- JSON
model
schema 1.0
type user
type group
relations
define member as self
type folder
relations
define parent as self
define viewer as self or viewer from parent
type document
relations
define parent as self
define viewer as self
define can_read as viewer or viewer from parent
{
"type_definitions": [
{
"type": "user",
"relations": {}
},
{
"type": "group",
"relations": {
"member": {
"this": {}
}
}
},
{
"type": "folder",
"relations": {
"parent": {
"this": {}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
}
}
},
{
"type": "document",
"relations": {
"parent": {
"this": {}
},
"viewer": {
"this": {}
},
"can_read": {
"union": {
"child": [
{
"computedUserset": {
"object": "",
"relation": "viewer"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
}
}
}
],
"schema_version": "1.0"
}
[
// Bob is a member of the Sales group
{
"user": "user:bob",
"relation": "member",
"object": "group:sales",
},
// The "pricing" document is in "sales" folder
{
"user": "folder:sales",
"relation": "parent",
"object": "document:pricing",
},
// Members of the "sales" team can view the "sales" folder
{
"user": "group:sales#member",
"relation": "viewer",
"object": "folder:sales",
},
// John can view the "pricing" document
{
"user": "user:john",
"relation": "viewer",
"object": "document:pricing",
},
]
Those tuples match the intent of how the model was designed, but without type restrictions we can also write tuples that would not. For example, we can say that a document is a member of the sales group:
[
// The "pricing" document is a member of the "sales" group
{
"user": "document:pricing",
"relation": "member",
"object": "group:sales",
},
]
To be able to better validate tuples and make the model more readable, version 1.1 requires you to specify types for all the relations that were previously assignable (e.g. relations defined as self
in any way), and it removes the as self
keyword.
The model above needs to be rewritten as:
- DSL
- JSON
model
schema 1.1
type user
type group
relations
define member: [user]
type folder
relations
define parent: [folder]
define viewer: [user] or viewer from parent
type document
relations
define parent: [folder]
define viewer: [user]
define can_read: viewer or viewer from parent
{
"mode": {
"schema_version": "1.1"
},
"type_definitions": [
{
"type": "user",
"relations": {}
},
{
"type": "group",
"relations": {
"member": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "folder",
"relations": {
"parent": {
"this": {}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "document",
"relations": {
"parent": {
"this": {}
},
"viewer": {
"this": {}
},
"can_read": {
"union": {
"child": [
{
"computedUserset": {
"object": "",
"relation": "viewer"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"can_read": {
"directly_related_user_types": []
}
}
}
}
],
"schema_version": "1.1"
}
After making these changes, Auth0 FGA will start validating the tuples more strictly, for example, you won’t be able to assign a document
as a member of a group
. If your application is writing invalid tuples, you’ll start getting errors when invoking the Write
API.
Disallowing String Literals in user_ids
With version 1.0 models, you could write a tuple where the user id did not specify a type, for example:
[
// "bob" is a member of the "sales" group
{
"user": "bob",
"relation": "member",
"object": "group:sales",
},
]
However, with version 1.1 you always need to specify an object, so “bob’” is no longer a valid identifier. If you don’t have a type in your model that defines relations for users, you can add a ‘user’ type with no relations to your model, for example:
- DSL
- JSON
type user
{
"type_definitions": [
{
"type": "user",
"relations": {}
}
]
}
You can then use that type when writing tuples:
[
// "user:bob" is a member of the "sales" group
{
"user": "user:bob",
"relation": "member",
"object": "group:sales",
},
]
Enforcing Type Restrictions
With the model above, the following tuples will be valid according to the type definitions:
[
{
"user": "user:bob",
"relation": "member",
"object": "group:sales",
},
{
"user": "folder:sales",
"relation": "parent",
"object": "document:pricing",
},
{
"user": "user:john",
"relation": "viewer",
"object": "document:pricing",
},
]
However, the one below will not be valid, as we can’t assign group:sales#member
to the viewer relationship of a folder.
[
{
"user": "group:sales#member",
"relation": "viewer",
"object": "folder:sales",
},
]
You might think that given group:sales#member
are actually users, you should still be able to assign it. Auth0 FGA calls expressions like group:sales#member
"usersets", and with our model we can only assign users.
The issue is that there are a lot of other usersets that you don't want to be assigned as viewers of a folder. For example, you would not want to add document:pricing#viewer
as viewers of the folder as conceptually it does not make sense to say “every viewer of this document should be a viewer of this folder”.
To allow these tuples to be written, you need to specify group#member
as a valid type for the folder’s viewer relationship. You would want to do the same with the document’s viewer relationship if you want to define that the members of a group can be viewers of a document:
- DSL
- JSON
model
schema 1.1
type user
type group
relations
define member: [user]
type folder
relations
define parent: [folder]
define viewer: [user,group#member] or viewer from parent
type document
relations
define parent: [folder]
define viewer: [user,group#member]
define can_read: viewer or viewer from parent
{
"type_definitions": [
{
"type": "user",
"relations": {}
},
{
"type": "group",
"relations": {
"member": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "folder",
"relations": {
"parent": {
"this": {}
},
"viewer": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "group",
"relation": "member"
}
]
}
}
}
},
{
"type": "document",
"relations": {
"parent": {
"this": {}
},
"viewer": {
"this": {}
},
"can_read": {
"union": {
"child": [
{
"computedUserset": {
"object": "",
"relation": "viewer"
}
},
{
"tupleToUserset": {
"tupleset": {
"object": "",
"relation": "parent"
},
"computedUserset": {
"object": "",
"relation": "viewer"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "folder"
}
]
},
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "group",
"relation": "member"
}
]
},
"can_read": {
"directly_related_user_types": []
}
}
}
}
],
"schema_version": "1.1"
}
You can identify which usersets you need to add by looking at tuples in your store that have the following structure:
[
// Members of the "sales" group are viewers of the "sales" folder
{
"user": "group:sales#member",
"relation": "viewer",
"object": "folder:sales",
},
]
If you find a tuple like that, you’ll need to add group#member
in the list of types allowed in the viewer
relation of the folder
type.
Public Access
When using version 1.0, you can indicate public access to specific objects by specifying a wildcard user in a relationship to any object, e.g.:
[
// All users are viewers of the "pricing" document
{
"user": "*",
"relation": "viewer",
"object": "document:pricing",
},
]
When you write the tuple above, all users are granted the “viewer” relationship for the “pricing" document. You can write those kinds of tuples for any relation that is directly assignable in the model.
In version 1.1 we want to be more explicit about the tuples you can write, so you’ll need to declare in the DSL which relations allow wildcards and for which object types. If we want to let any object of type “user” to be a viewer of a specific document, we’ll need to explicitly define it.
- DSL
- JSON
model
schema 1.1
type user
type document
relations
define viewer: [user,user:*]
{
"type_definitions": [
{
"type": "user",
"relations": {}
},
{
"type": "document",
"relations": {
"viewer": {
"this": {}
}
},
"metadata": {
"relations": {
"viewer": {
"directly_related_user_types": [
{
"type": "user"
},
{
"type": "user",
"wildcard": {}
}
]
}
}
}
}
],
"schema_version": "1.1"
}
You’ll need to specify user:*
as the user value in the tuple to enable this:
[
// All objects of type "user" are viewers of the "pricing" document
{
"user": "user:*",
"relation": "viewer",
"object": "document:pricing",
},
]
Being explicit about the wildcard type restrictions also lets you model scenarios like “all employees can see this document, but not all external users”, “all user accounts can access this document, but not service/machine-to-machine accounts”.
This change implies that you’ll need to change your code to write tuples with this new syntax, and that you’ll need to migrate existing tuples to use the new format.
You might have 3 kinds of tuples in your model that use *
, with different migration strategies:
- Tuples that have user =
*
You would need to retrieve those tuples and write them using the proper type (e.g. user:*
). To retrieve them, you’ll need to use the Read endpoint, filter on your side the tuples that have user = “*”
, and call the Write API for each one, with the proper type, e.g:
[
// All objects of type "user" are viewers of the "pricing" document
{
"user": "user:*",
"relation": "viewer",
"object": "document:pricing",
},
]
- Tuples that have user =
employee:*
, whereemployee
is NOT a type that is defined in the new iteration of your model.
If you have tuples with this format, they will be considered invalid because they don’t have a corresponding type in the model. If you need such a type defined, you’ll need to add it to the model, and the scenario will be similar to the one described below.
- Tuples that have user =
user:*
, which would mean the user with user_id =*
, whereuser
is type that is defined in the new iteration of your model.
In this case, the meaning of the tuple will change. If you were intending to specify a user with user id = *
, you will need to encode it in a different way instead of using *
. If you intended to specify public access to the object (e.g. "every user has this relationship with this object"), 1.0 models will not interpret the user:*
value as a wildcard but 1.1 models will.
If you have any wildcard tuples (i.e., *
or user:*
) that were created with model schema 1.0, you must delete and re-add these tuples with the appropriate type. This allows Auth0 FGA to interpret these tuples appropriately with the model schema 1.1 semantics. Failure to delete and re-add may cause Auth0 FGA to interpret these tuples incorrectly.
Query Evaluation Behavior with Type Restrictions
It is possible to introduce new models and have existing tuples (from prior models) with incompatibilities with the new model. Some cases where this can happen include:
- If you rename/delete a type.
- If you rename/delete a relation.
- If you remove a type restriction from the list of type restrictions for a relation, including changes for public access.
- If Auth0 FGA introduces a change that makes a tuple invalid.
In these cases, Auth0 FGA will not consider those invalid tuples when evaluating queries (Check, Expand, List-objects, etc). However, after any of the changes above happen, you should delete those tuples as having a large number of invalid tuples will negatively affect performance.
Improved Schema Validation
Type restrictions allow Auth0 FGA to validate the schema better at the time of writing the schema instead of at the time of query evaluation.
In Auth0 FGA, when referencing relations on related objects the relation tying the related objects (the word after from
, also called the tupleset) cannot be evaluated - that means it cannot be referencing another relation, or allow non-concrete types (type bound public access (<object_type>:*
) or usersets (<object_type>#<relation>
)) in its type restrictions.
In schema 1.0, because type restrictions were not available, the validation error would occur at the time of evaluation (e.g. a Check
call), while in schema 1.1, the error will be thrown when writing the model (during the WriteAuthorizationModel
request).
Check
call), while in schema 1.1, the error will be thrown when writing the model (during the WriteAuthorizationModel
request).- DSL
- DSL (Version 1.0)
- JSON
model
schema 1.1
type user
type folder
relations
define parent: [folder,folder#parent]
define editor: [user] or editor from parent
model
schema 1.0
type user
type folder
relations
define parent as self
define editor as self or editor from parent
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "folder",
"relations": {
"parent": {
"this": {}
},
"editor": {
"union": {
"child": [
{
"this": {}
},
{
"tupleToUserset": {
"tupleset": {
"relation": "parent"
},
"computedUserset": {
"relation": "editor"
}
}
}
]
}
}
},
"metadata": {
"relations": {
"parent": {
"directly_related_user_types": [
{
"type": "folder"
},
{
"type": "folder",
"relation": "parent"
}
]
},
"editor": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
}
]
}
In schema 1.0, the WriteAuthorizationModel
would have succeeded, but when attempting to write a tuple such as the one below, you would have received an error:
- Node.js
- Go
- .NET
- curl
- Pseudocode
await fgaClient.write({
writes: {
tuple_keys: [
{ user: 'folder:product#parent', relation: 'parent', object: 'folder:planning'}
]
},
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
body := fgaSdk.WriteRequest{
Writes: &fgaSdk.TupleKeys{
TupleKeys: []fgaSdk.TupleKey {
{
User: fgaSdk.PtrString("folder:product#parent"),
Relation: fgaSdk.PtrString("parent"),
Object: fgaSdk.PtrString("folder:planning"),
},
},
},
AuthorizationModelId: fgaSdk.PtrString("1uHxCSuTP0VKPYSnkq1pbb1jeZw")}
_, response, err := fgaClient.Auth0FgaApi.Write(context.Background()).Body(body).Execute()
if err != nil {
// .. Handle error
}
await fgaClient.Write(new WriteRequest{
Writes = new TupleKeys(new List<TupleKey>() {
new() { User = "folder:product#parent", Relation = "parent", Object = "folder:planning" }
}),
AuthorizationModelId = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});
curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \
-H "Authorization: Bearer $FGA_BEARER_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"writes": { "tuple_keys" : [{"user":"folder:product#parent","relation":"parent","object":"folder:planning"}] }, authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"}'
write([
{
"user":"folder:product#parent",
"relation":"parent",
"object":"folder:planning"
}
], authorization_model_id="1uHxCSuTP0VKPYSnkq1pbb1jeZw")
Deprecation Timeline
1- February 13th 2023: Deprecation Notice
- This deprecation notice is posted.
2- March 31st 2023: Disallow writing new 1.0 models
- The API will no longer accept writing new 1.0 models (affects
WriteAuthorizationModel
). - Evaluation requests (
Check
,Expand
,ListObjects
),WriteAssertions
andWrite
requests against earlier models will still work. ReadAuthorizationModel
,ReadAuthorizationModels
andReadAssertions
will continue to serve results for 1.0 and 1.1 models.- The model schema version will be required in all new authorization models.
3- May 1st 2023: Disallow evaluating 1.0 models
- Query evaluations of any 1.0 models (Check, Expand, ListObjects and Write) will no longer be accepted.
- ReadAuthorizationModel, ReadAuthorizationModels and ReadAssertions` will continue to serve results for 1.0 and 1.1 models.