This reference guides covers Common Expression Language (CEL) syntax relevant to
creating expressions for the @auth(expr:)
and @check(expr:)
directives.
Complete reference information for CEL is provided in the CEL specification.
Test variables passed in queries and mutations
@auth(expr)
syntax allows you access and test variables from queries and
mutations.
For example, you can include an operation variable, such as $status
, using
vars.status
.
mutation Update($id: UUID!, $status: Any) @auth(expr: "has(vars.status)")
Data available to expressions: request, response, this
You use data for:
- Evaluation with CEL expressions in
@auth(expr:)
and@check(expr:)
directives - Assignment using server expressions,
<field>_expr
.
Both @auth(expr:)
and @check(expr:)
CEL expressions can evaluate the
following:
request.operationName
vars
(alias forrequest.variables
)auth
(alias forrequest.auth
)
In mutations, you can access and assign the contents of:
response
(to check partial results in multi-step logic)
Additionally, @check(expr:)
expressions can evaluate:
this
(the value of the current field)response
(to check partial results in multi-step logic)
The request.operationName binding
The request.operarationName
binding stores the type of operation, either query
or mutation.
The vars
binding (request.vars)
The vars
binding allows your expressions to access all variables
passed in your query or mutation.
You can use vars.<variablename>
in an expression as an alias for the
fully-qualified request.variables.<variablename>
:
# The following are equivalent
mutation StringType($v: String!) @auth(expr: "vars.v == 'hello'")
mutation StringType($v: String!) @auth(expr: "request.variables.v == 'hello'")
The auth
binding (request.auth)
Authentication identifies users requesting access to your data and provides that information as a binding you can build on in your expressions.
In your filters and expressions, you can use auth
as an alias for
request.auth
.
The auth binding contains the following information:
uid
: A unique user ID, assigned to the requesting user.token
: A map of values collected by Authentication.
For more details on the contents of auth.token
see
Data in auth tokens
The response
binding
The response
binding contains the data being assembled by the server in
response to a query or mutation as that data is being assembled.
As the operation proceeds, as each step is completed successfully,
response
contains response data from successfully-completed steps.
The response
binding is structured according the shape of its associated
operation, including (multiple) nested fields and (if applicable) embedded
queries.
Note that when you access embedded query response data, fields can contain
any data type, depending on the data requested in the embedded query; when you
access data returned by mutation fields like _insert
s and _delete
s, they may
contain UUID keys, number of deletes, nulls (see the
mutations reference).
For example:
- In a mutation that contains an embedded query, the
response
binding contains lookup data atresponse.query.<fieldName>.<fieldName>....
, in this case,response.query.todoList
andresponse.query.todoList.priority
.
mutation CheckTodoPriority(
$uniqueListName: String!
) {
# This query is identified as `response.query`
query @check(expr: "response.query.todoList.priority == 'high'", message: "This list is not for high priority items!") {
# This field is identified as `response.query.todoList`
todoList(where: { name: $uniqueListName }) {
# This field is identified as `response.query.todoList.priority`
priority
}
}
}
- In a multi-step mutation, for example with multiple
_insert
fields, theresponse
binding contains partial data atresponse.<fieldName>.<fieldName>....
, in this case,response.todoList_insert.id
.
mutation CreateTodoListWithFirstItem(
$listName: String!,
$itemContent: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()",
name: $listName,
})
# Step 2:
todo_insert(data: {
listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
content: $itemContent,
})
}
The this
binding
The binding this
evaluates to the field that the @check
directive
is attached to. In a basic case, you might evaluate single-valued query
results.
mutation UpdateMovieTitle (
$movieId: UUID!,
$newTitle: String!)
@auth(level: USER)
@transaction {
# Step 1: Query and check
query @redact {
moviePermission( # Look up a join table called MoviePermission with a compound key.
key: {movieId: $movieId, userId_expr: "auth.uid"}
) {
# Check if the user has the editor role for the movie. `this` is the string value of `role`.
# If the parent moviePermission is null, the @check will also fail automatically.
role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
}
}
# Step 2: Act
movie_update(id: $movieId, data: {
title: $newTitle
})
}
If the returned field occurs multiple times because any ancestor is a list, each
occurrence is tested with this
bound to each value.
For any given path, if an ancestor is null
or []
, the field won't be
reached and the CEL evaluation will be skipped for that path. In other words,
evaluation only takes place when this
is null
or non-null
, but never
undefined
.
When the field itself is a list or object, this
follows the same structure
(including all descendants selected in case of objects), as illustrated in the
following example.
mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
# Step 1: Query and check
query {
moviePermissions( # Now we query for a list of all matching MoviePermissions.
where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
# This time we execute the @check on the list, so `this` is the list of objects.
# We can use the `.exists` macro to check if there is at least one matching entry.
) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
role
}
}
# Step 2: Act
movie_update(id: $movieId, data: {
title: $newTitle
})
}
Complex expression syntax
You can write more complex expressions by combining with the &&
and ||
operators.
mutation UpsertUser($username: String!) @auth(expr: "(auth != null) && (vars.username == 'joe')")
The following section describes all available operators.
Operators and operator precedence
Use the following table as a reference for operators and their corresponding precedence.
Given arbitrary expressions a
and b
, a field f
, and an index i
.
Operator | Description | Associativity |
---|---|---|
a[i] a() a.f |
Index, call, field access | left to right |
!a -a |
Unary negation | right to left |
a/b a%b a*b |
Multiplicative operators | left to right |
a+b a-b |
Additive operators | left to right |
a>b a>=b a<b a<=b |
Relational operators | left to right |
a in b |
Existence in list or map | left to right |
type(a) == t |
Type comparison, where t can be bool, int, float,
number, string, list, map, timestamp, or duration |
left to right |
a==b a!=b |
Comparison operators | left to right |
a && b |
Conditional AND | left to right |
a || b |
Conditional OR | left to right |
a ? true_value : false_value |
Ternary expression | left to right |
Data in auth tokens
The auth.token
object may contain the following values:
Field | Description |
---|---|
email |
The email address associated with the account, if present. |
email_verified |
true if the user has verified they have access to the email address. Some providers automatically verify email addresses they own. |
phone_number |
The phone number associated with the account, if present. |
name |
The user's display name, if set. |
sub |
The user's Firebase UID. This is unique within a project. |
firebase.identities |
Dictionary of all the identities that are associated with this user's account. The keys of the dictionary can be any of the following: email , phone , google.com , facebook.com , github.com , twitter.com . The values of the dictionary are arrays of unique identifiers for each identity provider associated with the account. For example, auth.token.firebase.identities["google.com"][0] contains the first Google user ID associated with the account. |
firebase.sign_in_provider |
The sign-in provider used to obtain this token. Can be one of the following strings: custom , password , phone , anonymous , google.com , facebook.com , github.com , twitter.com . |
firebase.tenant |
The tenantId associated with the account, if present. For example, tenant2-m6tyz |
Additional fields in JWT ID tokens
You can also access the following auth.token
fields:
Custom Token Claims | ||
---|---|---|
alg |
Algorithm | "RS256" |
iss |
Issuer | Your project's service account email address |
sub |
Subject | Your project's service account email address |
aud |
Audience | "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit" |
iat |
Issued-at time | The current time, in seconds since the UNIX epoch |
exp |
Expiration time |
The time, in seconds since the UNIX epoch, at which the token expires. It
can be a maximum of 3600 seconds later than the iat .
Note: this only controls the time when the custom token itself expires. But once you sign a user in using signInWithCustomToken() , they will remain signed in into the
device until their session is invalidated or the user signs out.
|
<claims> (optional) |
Optional custom claims to include in token, which can be accessed through
auth.token (or request.auth.token ) in
expressions. For example, if you create a custom claim
adminClaim , you can access it with
auth.token.adminClaim .
|