Tokens and role membership
In this section, you write an simple application that uses the authentication
functions you added to the CoffeeStore database. If you are working through the
cookbook sequentially, you have already written simple Javascript application
using the fauna
module.
Application authentication
In this section, you update the coffeestore.mjs
file to sign in and then log
in a user.
-
Edit the
coffeestore.mjs
file and simplify the connection section to this:import { Client, fql } from "fauna"; const envSecret = process.env.FAUNA_SECRET; // Configure your client const client = new Client({ endpoint: new URL("https://db.fauna.com"), secret: envSecret});
The application uses an admin secret to make the initial connection to Fauna.
-
Replace the try/catch block with the following code.
try { // Build queries using the fql function // Sign a user up const signup_query = fql`Signup("Hector Binding", "hbinding@gmail.com", "sekret")`; // execute the query const signup_result = await client.query(signup_query); console.log("CREDENTIAL created:"); console.log(signup_result); // Log the user in and get back the token const login_query = fql`loginUser("hbinding@gmail.com", "sekret")`; const login_result = await client.query(login_query); console.log("TOKEN created:"); console.log(login_result); // Return the token secret for testing purposes const just_data = login_result.data; console.log("Token SECRET: " + just_data.secret); // Make a new client with the user token const loginClient = new Client({ const loginClient = new Client({ endpoint: new URL("https://db.fauna.com"), secret: just_data.secret}); // Create a query to locate the user token const token_query = fql`Query.token()`; //Return the token const token_result = await loginClient.query(token_query); console.log("Query.token() returns:"); console.log(token_result); } catch (error) { console.log(error) }
This code authenticates users in a server-side JavaScript application. You would use a similar approach if you’re using Fauna with other programming languages.
-
Run the application:
node coffeestore.mjs
You should response similar to the following:
CREDENTIAL created: { data: Document { coll: Module { name: 'Credential' }, id: '372736323917709345', ts: TimeStub { isoString: '2023-08-11T04:25:08.950Z' }, document: DocumentReference { coll: [Module], id: '372736323915612193' } }, static_type: 'Credential', summary: '', txn_ts: 1691727908950000, stats: { compute_ops: 1, read_ops: 0, write_ops: 2, query_time_ms: 92, contention_retries: 0, storage_bytes_read: 601, storage_bytes_write: 808, rate_limits_hit: [] }, schema_version: 1691691883450000 } TOKEN created: { data: Document { coll: Module { name: 'Token' }, id: '372736324061364257', ts: TimeStub { isoString: '2023-08-11T04:25:09.090Z' }, secret: 'fnEFLDm2PpAAIQUh3mNk4AAh-t4fkze2d-ZU8YI4C0tK2PxEMmk', document: DocumentReference { coll: [Module], id: '372736323915612193' } }, static_type: 'Token', summary: '', txn_ts: 1691727909090000, stats: { compute_ops: 1, read_ops: 2, write_ops: 1, query_time_ms: 89, contention_retries: 0, storage_bytes_read: 791, storage_bytes_write: 433, rate_limits_hit: [] }, schema_version: 1691691883450000 } Token SECRET: fnEFLDm2PpAAIQUh3mNk4AAh-t4fkze2d-ZU8YI4C0tK2PxEMmk Query.token() returns: { data: Document { coll: Module { name: 'Token' }, id: '372736324061364257', ts: TimeStub { isoString: '2023-08-11T04:25:09.090Z' }, document: DocumentReference { coll: [Module], id: '372736323915612193' } }, static_type: '{ *: Any } | Null', summary: '', txn_ts: 1691727909139652, stats: { compute_ops: 1, read_ops: 1, write_ops: 0, query_time_ms: 6, contention_retries: 0, storage_bytes_read: 597, storage_bytes_write: 0, rate_limits_hit: [] }, schema_version: 1691691883450000 }
In the output, you can see that the
372736323915612193
document id is associated with the credential and the token. This is the id belonging to the People document associated with the token. -
Go into the Dashboard Shell and query for the user referenced by the token.
{ id: "372736323915612193", coll: People, ts: Time("2023-08-11T04:25:08.950Z"), name: "Hector Binding", email: "hbinding@gmail.com" }
-
You can also find the
Credential
for this user.{ id: "372736323915612193", coll: People, ts: Time("2023-08-11T04:25:08.950Z"), name: "Hector Binding", email: "hbinding@gmail.com" }
Like a token, this document never expires. You can pass a ttl
value to the
Credential.login()
call to expire the token.
Token secret storage
After your code obtains the token secret
for the user, you can use it for
subsequent queries to Fauna on behalf of the user. Because this code did not
set the ttl
value, its default value is null
, which causes the document to
persist indefinitely or until deleted.
You have many options for storing the secret:
-
Pure client-side: If you intend on accessing Fauna client-side, you could send the secret back to the client and store it in memory.
-
Partial backend with cookie: If you are working on a server API, you could store the secret in a session and send it back to the client using a secure cookie.
-
Partial backend with
httpOnly
cookie: Combine the preceding two approaches by creating two types of tokens in Fauna. One that could be used as a refresh token and stored in an httpOnly cookie, and another short-lived one that could be used and stored in the frontend. -
Entirely backend: You could also decide that you never want your clients receiving the secret and store the session in some cache and send back a session ID.
These examples have different security implications which are far too vast and complex to discuss here. Also, solutions depend on your application use case. Consider carefully which of the solution is best for you.
Role membership
When a query is made with Token
, Fauna evaluates the stored Credential
to authenticate a caller. Role membership
and privileges
are evaluated to
grant authorization. Recall the humanResources
role currently has this
configuration:
{
name: "humanResources",
coll: Role,
ts: Time("2023-07-21T00:22:25.260Z"),
privileges: {
resource: "People",
actions: {
read: true,
create: "data => data.employment == \'active\' "
}
}
}
Anyone holding a key with the humanResources
role can read the People
collection and create an element in it as long as the element is active.
The secret, fnEFLDm2PpAAIQUh3mNk4AAh-t4fkze2d-ZU8YI4C0tK2PxEMmk
, for the
Hector
user cannot read the People
collection. You can test this.
-
Go to the Dashboard Shell and run the
People.all()
query asAdmin
.If you are working through this cookbook, the
People
collection should have several documents. -
Run the query again with the secret your application returned for
Hector
.You only see the
Hector
user. The user token can only see the document whoseid
matches the one found in the corresponding credential. To allowHector
to see the completePeople
collection, you must update thehumanResources
role. -
Switch to an
Admin
key in the shell, update the role.{ name: "humanResources", coll: Role, ts: Time("2023-08-11T05:27:52.160Z"), privileges: { resource: "People", actions: { read: true, create: "data => data.employment == \'active\' " } }, membership: [ { resource: "People" } ] }
This update specifies a
membership
which grants everyone inPeople
the ability to list all the documents in the collection. -
Switch the Shell back to the secret your application returned for
Hector
. -
Run the
People.all()
query again.This time
Hector
should see all the people.
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!