DEV Community

marvelken
marvelken

Posted on • Edited on

AUTHENTICATION WITH FAUNA

Author;- Marvel Ken.

Introduction

Fauna is a general-purpose, distributed database supporting multiple data models and strong
global consistency for modern app developers in the server-less era. Fauna uses many
coding languages like JavaScript, Nordjs, Java, C#, Scala, Python, GraphQL.
Fauna provides authentication and authorization features that makes it easy to provide clients
with direct access to your database in a secure and controlled way. This tutorial shows you
how to use user authentication and control access to information with attribute-based access
control (ABAC).

User authentication

Fauna offers built-in identity, authentication, and password management. In user
Authentication, we will look at creating user identity, authenticating them, and managing
seasons. We will divide this part into sections: setting up, creating user, user login, user
logout, changing user password, checking user credentials, and third-party delegation.

SETTING UP

The setup section describes all the preparatory work needed for authenticating the user. They
include: creating a database, creating a server key, creating a client key, creating a collection
to store user documents, and creating a public index for users.
First you need to sign up for a free Fauna account.

Creating a databases

In user authentication, We need the context of a specific application. With this, we can create
an application-specific database and call it “APPA.” the following query will create our
database:
my_db>CreateDatabase({
name: "APPA"
})

Creating a server key

In user authentication, We need the context of a specific application. With this, we can create
an application-specific database and call it “APPA.”
Our application is going to need access to our new database though We don’t need to permit
all databases. We create a “server” key that will full access to a specific database.
This query will create our server key:
my_db>CreateKey({
name: "Server key for APPA",
database: Database("APPA"),
role: "server",
})

After you run the query, different values will be generated. The values need to be copied for

the secret field. This is the key that authorizes Fauna, Specifically the associated database.
Note that this key is only generated once if the lost new key needs to be developed.

Creating a client key

We need to allow applications, public clients, typically a web browser, to access our “APPA”
database and enable a key into the public client to permit the access. When creating a client
key, the following query is written in the shell. After running the query we need to copy the
security field results, since it is displayed once.
my_db>CreateKey({
name: "Client key for app1",
database: Database("APPA"),
role: "client",
})

Creating a collection to store user documents.

After creating an App-specified database and keys to access it, we need to create a collection
to store user documents. We use our saved server key to log in to the database and then
create a collection to store user documents using the following query:
my_db> CreateCollection({ name: "users" })

Creating a public public index for our user

We need an index to make it possible to look at our user by their email address. We need this
index to be public, since unauthorized users would be using the client key when attempting to
log in, that's why we need the index. We create it using the following query:
my_db> CreateIndex({
name: "users_by_email",
permissions: { read: "public"},
source: Collection("users"),
terms: [{field: ["data", "email"]}],
unique: true,
}

USER LOGIN

When a user wants to log in, they need to provide their email and password. Then we use the
login function to authenticate their access, and if valid, give them a token that they can use to
access resources.
A token only provides access according to the privileges granted by an attribute-based
access control(ABAC) role. This differs from keys, which are used to offer a coarser
database_level_access. The following query calls Login on the result of looking up the user
via the users_by_email index, with the password that they provided:
my_db> Login(
Match(Index("users_by_email"), "samson@site.example"),
{ password: "secret password" },
)

After you run the query, the following development will be displayed:

{ ref: Ref(Tokens(), "251407817091580416"),
ts: 1576020028130000,
instance: Ref(Collection("users"), "251407645221585408"),
secret: 'fnEDfS4T34ACAAN9IwrU8aQA5SxTgyqYaUfiAqLqzQjQH9Qcr94'}
If the user cannot be found, or if their credentials are invalid, an error would be returned:
my_db> Login(
Match(Index("users_by_email"), "samsam@not.a.member"),
{ password: "secret password" },
)

results:
[{ position: [],
code: 'authentication failed',
description:
'The document was not found or provided password was incorrect.' }]

USER LOG OUT

After you call Logout, All the tokens associated with the current session are invalidated,
effectively logging out the user. A new token would need to be created for any future access.
Logout takes a single parameter all_tokens. When all_tokens are true, all tokens associated
with the current user are invalidated, logging the user out completely. When all_tokens are
false, only the current token is invalidated; any other active tokens are still valid.
Only call Logout when connecting to Fauna with a token received from calling Login. In your
client application code, that query would look similar to this JavaScript code:
my_db> client.query(q.Logout(true))
When you execute this query, a response of true indicates that log out was successful, and
false indicates that log out failed.

CHANGING A USER PASSWORD

Changing a user’s password is done by calling the Update or Replace functions with a new
password in the credentials field. When a password is updated, any existing tokens remain
valid.
To change the user password, the following query can be used:

my_db> Update(
Ref(Collection("users"), "251407945291585008"),
{
credentials: { password: "new password" },
}
)
After changing password, we need to get a new token based on the new password, using
the following query:
my_db> Login(
Match(Index("users_by_email"), "samson@site.example"),
{ password: "new password" },
)

CHECKING CREDENTIALS

Users can be verified without creating a token, and this is done by calling the identity function.
For example, we can test whether the old and new credentials for our user are valid using the
following query:
my_db>[
identity(
Ref(Collection(“users”),”251423556667638838666”),
“secret password”,
)
identity(
Ref(Collection(“users”),”251423567546676388386”),
“new password”,
)
]

After running this query the result should be either true or false.

THIRD PARTY DELEGATION

Third-party delegation is the scenario where a third party uses our APIs to provide services to
our users.
Using the authentication features of Fauna, we can provide unique tokens for each third-party
client that allow the third party to access resources on behalf of our users, while providing a
way for the user to revoke the third-party client’s access.
First, we create an index that allows us to list all of a user’s tokens. Login allows us to attach
data to a token by adding extra fields. We’ll use this capability to identify our tokens with the
name of the third-party service that will use the tokens.
EXAMPLE:
my_db> CreateIndex({
name: "tokens_by_instance",

permissions: { read: "public" },
source: Tokens(),
terms: [{ field: "instance" }],
values: [{field: ["data", "name"]}]
})

Now we can create a token for each third-party service that our user uses. And we can do it
all in a single query.
The following query can be used:
my_db> Map(
[
"Desktop App",
"Mobile App",
"Web Service"
],
Lambda(
"service",
Login(
Match(Index("users_by_email"), "samson@site.example"),
{
password: "new password",
data: { name: Var("service") }
}
)

Finally, in client application code, list all the currently logged-in user’s tokens by querying the
index that we built, when connecting to Fauna using the user’s token. The following code is
written in JavaScript:
my_db>client.query(
q.Paginate(
q.Match(
q.Index("tokens_by_instance"),
q.Select("instance", q.Identity())
)
)
)
)
.then((ret) => console.log(ret))
.catch((err) => console.log("Error:", err))
After executing this query in your client application code, after the user has logged in
successfully, the output should be:
{ data: [ 'Desktop App', 'Mobile App', 'Web Service' ] }

Access to Information with Attribute-Based Access Control(ABAC).

Attribute-based access control (ABAC) is a flexible, fine-grained strategy for managing
identity-based operations within Fauna. ABAC extends the default Fauna authentication and
authorization mechanisms.

ABAC has a role-based access control (RBAC), where roles can define privileges that can be
dynamically determined based on any attribute of the actor attempting to access or modify
data, any attribute of the data to be accessed or modified, or contextual information available
during a transaction.
A role defines a set of privileges-specific actions that can be executed on specific resources
and membership-specific identities that should have the specified privileges.
This can be done in the following sections; privilege section, membership section, Predicate
functions, Overlapping roles.
Privilege section
Privilege specifies the resource in Fauna, where the resource could be a database, collection,
document, key, index, function.
I. The actions available vary according to the target resource: Core schemas
(Databases, Collections, Indexes, Functions, Keys): create and delete
II. Documents: create, read, write, delete, history_read, history_write
III. User-defined functions: call
examples:
mydb> CreateRole({
name: "access_todos",
membership: [{ resource: Collection("users") }],
privileges: [{
resource: Collection("todos"),
actions: {
create: true,
delete: true,
write: true
}
}]
})

Membership section
Membership describes the set of documents that should have the role’s privileges.
Membership is managed with collection; documents in the collection are members of the role.
Typically, a "document" would refer to a "user", but a document can be any record within
Fauna.
Membership can also be controlled with a predicate function for dynamic membership
evaluation. Multiple roles can be associated with a Fauna resource, and users can be

associated with multiple roles. Attribute-based access is computed for every Fauna
transaction, and updates to the role configuration take effect immediately. Also, action
permissions can be computed dynamically via Lambda functions.
Example:
my_db>CreateRole({
name: "can_manage_todos",
membership: [
{
resource: Collection("users"),
predicate: Query(Lambda(ref =>
Select(["data", "vip"], Get(ref))
))
}
],
privileges: [
// ...
]
})

Predicate functions
A predicate function is the FQL Lambda function that operates in a read-only fashion,
accepting command-specific arguments, and returning true or false to indicate whether the
action is permitted or prohibited. The actions and their associated arguments are:

-Create: the new data that is about to be created.
-Read, history_read, delete: the ref to the underlying document.
-Read for indexes: the terms being used to match against the index.
-Write, history_write: the old data, the new data, and a reference to the document to
be written.
-Call: the parameters to be passed to the user-defined function.
Example:

mydb> CreateRole({
name: "can_manage_todos",
membership: [
// ...
],
privileges: [
{
resource: Collection("todos"),
actions: {
create: Query(Lambda(newData =>
Select(["data", "vip"], Get(Identity()))
)),
// ...
}
}
]
}

Example role

mydb> CreateRole({
name: "users",
membership: [
{
// This role will be assigned to all users
// as long as they are active
resource: Collection("users"),
predicate: Query(ref =>
Select(["data", "isActive"], Get(ref), false)
)
}
],
privileges: [
{
resource: Collection("todos"),
actions: {
write:
// The following function enforces that you can write to your
// own data but, you can't change the owner of the data
Query((oldData, newData) =>
And(
Equals(
Identity(),
Select(["data", "owner"], oldData)
),
Equals(
Select(["data", "owner"], oldData),
Select(["data", "owner"], newData),
)
)
)
}
}
]
})

Overlapping roles.

When a document is a member of two or more roles, Fauna does its best to optimize for the
most common access pattern, to avoid evaluating roles especially predicates unnecessarily.
The general approach is that once permission to perform a specific action for a specific
resource has been granted, no further determinations for that resource+action need to be
performed the first granted access wins.

The table stores the resolution of permissions, keyed on the combination of resource identifier
and requested action, involved in the current query.
During role processing, if a resource+action permission evaluates to true, or can be trivially
determined to be true, the permission is granted and no further evaluations for that specific
resource+action are performed.

CONCLUSION

We have come to the end of our project. We covered all you need to know about
authentication with Fauna. The above tutorial exemplified how to use user authentication and
control access to information with attribute-based access control (ABAC).
Written in connection with the Write with Fauna Program.

Top comments (0)