Tutorial: User Authentication

User Authentication

User authentication in Flitter is provided by the flitter-auth package.

flitter-auth provides a framework for provider-agnostic, role-permission user authentication for your application. It includes an Auth Provider API to allow custom login sources that use the same User model so your app can work seamlessly. flitter-auth ships with Flitter by default, so to get started, just run the ./flitter deploy auth command.

Getting Started

Setup Auth Framework

To add the auth framework to your app, run the deployment: ./flitter deploy auth. This will create the necessary routes, views, models, middleware, and controllers.

Configuration

You'll probably want to start out by editing the config/auth.config.js file to suit your setup. By default, flitter-auth ships with 3 providers: FlitterProvider, Oauth2Provider, and LdapProvider. LDAP and OAuth2 are pretty obvious, and the Flitter provider is just a local database registration/login system as you'd expect. FlitterProvider is enabled by default.

You can also change the default login provider. If you are using an external auth source, you may want to do this to make routing simpler.

This file is also where you can define roles as arrays of permissions. More on that later.

Routes

flitter-auth defines the routing/routers/auth/forms.routes.js router. This creates the view/logic endpoints for the registration and login forms that auth ships with by default (which you can customize in views/auth/). The basic structure is as follows:

/auth/{provider}/{action}

If no provider is specified, the system will use the default auth provider. So, for example, if I wanted to sign in with the LDAP provider, I could navigate to:

/auth/ldap/login

Likewise, to register with the default provider:

/auth/register

Using the User Model

You may notice that the flitter-auth user model looks a bit different from other Flitter models. That's because it inherits a base set of methods and fields from the default flitter-auth user model. (See: module:flitter-auth/model/User~BaseUser)

You can add any custom properties/methods you like to app/models/auth/User.model.js, but it should always have the base ones provided. App users are stored in a particular way with flitter-auth. Each user is assigned a universally-unique ID when they register/login for the first time. This uuid is what you should use in internal logic to access/refer to specific User instances. Why? Because of this important takeaway:

Usernames are not necessarily unique.

flitter-auth's base user model provides 3 identifying fields by default: uid, provider, and uuid. The uid and provider values are provided from the various authentication sources in your application. As such, they may not be unique. It's entirely possible to have a johndoe from the LDAP provider, and a johndoe from the Flitter provider. Referring to these users internally by their UUIDs solves this issue.

Roles & Permissions

flitter-auth provides a reasonably robust access-control system using roles and permissions. Let's get something out of the way:

permission = ability to access a specific (or class of) document or perform a specific (or class of) action

role = group of permissions assigned to a user

In Flitter, security is assigned at the permission level. A role is merely a configuration device used to group permissions together into helpful sets.

The structure of a permission

Permissions have a particular structure that comes in several parts. Let's look at an example:

uploaded_image:47

This permission is specified in 2 parts, split by the : character. The first part uploaded_image is the class of the permission. In this case, the permission covers access to uploaded image files. The second part, 47 is the particular case of the permission's class. In this case, the user has access to the uploaded image file with ID 47. These values are arbitrary and can be used to define any number of formats:

patient_records:view file:775839938754 contact_form:view_submissions

Assume you have some instance of the User class called user. You can give a user a specific permission like so:

user.allow('uploaded_image:47')

user.can('uploaded_image:47') // => true

Likewise, remove permissions:

user.disallow('uploaded_image:47')

user.can('uploaded_image:47') // => false

Side note: You don't need to specify a particular case for a permission to work as you'd expect. user.allow('can_login') is perfectly valid: user.can('can_login') // => true.

Scoped permissions

Permission class supercedes permission case. So, if a user is assigned only the class of a permission, they have access to all particular cases of that permission:

user.allow('uploaded_image')

user.can('uploaded_image:47') // => true
user.can('uploaded_image:889') // => true
user.can('uploaded_image') // => true

Note that this relationship does not work the other way;

user.allow('uploaded_image:47')

user.can('uploaded_image') // => false

Nested Permissions

Note that the number of levels in a permission is arbitrary, but the scoping still applies:

user.allow('uploaded_image:47:view')

user.can('uploaded_image:47') // false
user.can('uploaded_image:47:view') // true

Or:

user.allow('uploaded_image:47')

user.can('uploaded_image:47:view') // true

Roles

As mentioned above, roles are merely a configuration tool for assigning batches of permissions to users. They are configured in the config/auth.config.js#auth.roles config key. Say you had the following:

// in config/auth.config.js
{
	roles: {
		cms_user: ['uploaded_image'],
	}
}

Then, you can allow users access to that role's permissions en masse like so:

user.can('uploaded_image:47') // => false

user.promote('cms_user')
user.can('uploaded_image:47:view') // => true
user.can('uploaded_image') // => true

user.demote('cms_user')
user.can('uploaded_image:47') // => false