Authentication and Authorization

A developers guide to the User Authentication and Authorization processes

User Authentication

Form.io authentication provides a secure way to control access to forms and applications, ensuring that only authorized users can interact with sensitive data or perform specific actions on a Form. Form.io enables users to verify their identity using a variety of methods, including OAuth, SAML, LDAP, JWT tokens, or email/password. Here's how authentication typically functions:

  1. Authentication Method: The first step in the authentication process is to decide what authentication method will be used for your client-side application. Form.io offers a multitude of different methods to authenticate users. Typically, Form.io users prefer to integrate their own authentication provider such as OAuth or SAML, to authenticate their client-side users. The Form.io project is then integrated with the selected authentication provider to facilitate the authentication process

  2. Authentication Form Configuration: Once the authentication method is set up, form builders can configure Login forms to support the authentication method. This configuration will vary depending on the chosen authentication method, but usually involves adding Actions or specially configured buttons to the authentication form to trigger the provider login request.

  3. Authentication Request: When authentication is required, users are prompted to provide their credentials through the authentication form. Depending on the authentication method, this could involve entering user credentials for an OAuth or SAML provider, clicking a single sign-on (SSO) button, or entering an email address and password saved to a Form.io Resource or LDAP directory.

  4. Verification: Once the credentials are submitted, Form.io verifies them against its authentication system. This will typically involve communicating with an external identity provider, checking the credentials against a user database saved to a Form.io Resource, or validating a JWT token.

  5. Authentication Success: If the provided credentials are valid, Form.io grants access to the requested form or application by handing off a JWT access token. A Form.io Role is given to the user allowing them to interact with the form, submit data, or perform other actions as permitted by their authentication level and permissions. The role is distributed to the user based on the authentication method.

  6. Additional Security Measures: Form.io offers additional security measures such as Two-Factor Auth (2FA) or reCAPTCHA verification to enhance security and prevent unauthorized access.

Authentication Tokens

Form.io uses JSON Web Tokens (JWT) for all aspects of user authentication. When leveraging an external authentication method such as SAML or OAuth, the Form.io platform converts the provider tokens into equivalent JWT tokens. This token contains the JSON code with the User and Project ID. These IDs are then used to generate the user information saved to the user object which includes the Role. See the Authentication Flow detailed below for more information.

It is highly recommended to read how these tokens work to have a complete understanding of how the Authentication system within Form.io operates. Below are some resources to read regarding JWT.

Authentication Flow

The following diagram illustrates the flow for a Resource Based authentication method.

As this diagram illustrates, the mechanism that performs an authentication is achieved with the Login Action which is a form action that can be attached to any form within the Form.io platform. The series of steps that this Login action performs is as follows.

  1. Inspects a POST body of the submission API call that was made against the authentication form.

  2. Extract the "username" and "password" fields from the POST body. These fields are configured as part of the Login action configuration.

  3. Perform a search against the underlying Resource to match a submission for the provided "username". The underlying resources are also configured as part of the Login action allowing you to authenticate against ANY resource.

  4. Once a record has been found, it will then perform a one-way encrypted hash comparison between the password fields of that resource to ensure that the user who submitted the form has matched the password on that resource submission.

  5. If the password hashes match, then the Login Action will then generate a JWT token of the resource submission object which will then become the User that is authenticated.

  6. Assign that JWT token to the "x-jwt-token" response header which is then sent back to the client application.

  7. The renderer will see that a "x-jwt-token" has been introduced and then save this token to the "formioToken" localStorage variable which will be used as the persistent authentication.

This JWT token is then used in every request made to the Form.io platform, which will serve as the authentication context for any request made into Form.io. If you wish to view the JWT token for any application that you are using connected to Form.io, you can navigate to the Local Storage of your browser and take a look at the formioToken localStorage variable like the following shows.

The JWT token can also be read as raw JSON by copying the value of the JWT Token and then taking it to https://jwt.io to view the contents like the following illustrates.

This JWT token forms the basis behind all authentication within Form.io. Even when other authentication providers are utilized such as SAML, Oauth, and LDAP, the goal of those actions is to exchange the tokens of those systems into a JWT token which is then used by the Form.io platform to establish authentication of an end user.

User Authorization

User authorization occurs after the authentication process has been completed. Form and Application authorization access is determined by the user's role, which is assigned to a user after authentication. These Roles are linked to specific Permissions configured within the Forms of your Project. Permissions grant the User the ability to interact with forms, submit data, or perform other actions within both the forms and application. Here's how authorization typically works after the User Authentication process is complete.

Before you read this section, we highly recommend you read the Roles and Permissions documentation to become familiar with the mechanics of how Users, Roles, and Permissions are handled within Form.io.

  1. Access Roles: After successfully authenticating, Form.io grants the user access to the requested application by handing off a JWT authentication token. During this process, the user is assigned a Form.io Role which is an array of MongoDB IDs saved to the User object. These Roles are created and managed within the Form.io project. When authentication methods like OAuth or SAML are utilized, the authentication provider Role is mapped to a Form.io role. A typical user object within Form.io may look like the following. Take note of the Role ID in line 6.

{
  "_id": "661ffe3b2adabc01222babcd",
  "form": "6347275deaed4f145892abcd",
  "owner": "5bf5a5aa46322444b9f1abcd",
  "roles": [
    "6347275deaed4fe4ab926673"
  ],
  "access": [],
    "login": {
      "attempts": 0,
      "last": 1713372778481
    }
  },
  "data": {
    "email": "joe@example.com"
  },
  "_fvid": 1,
  "project": "6347275deaed4fff5592asbcd",
  "state": "submitted",
  "externalIds": [],
  "created": "2024-04-17T16:52:11.998Z",
  "modified": "2024-04-17T16:52:11.998Z",
  "isAdmin": false,
  "onlyPrimaryWriteAccess": false
}
  1. Access Permissions: The user-assigned Role is mapped to various Permissions configured within the Form.io project. These permissions dictate the operations (Create, Read, Update, Delete, and Index) that can be performed by the authenticated user on forms and their associated submission data.

Session Management

The Form.io platform ensures that every authentication within the platform is associated with a current "session". A Session is a mechanism to connect authenticated requests made by the same JWT token and ensures that "sessions" can be invalidated once a login occurs. This works by adding a Session ID to each JWT token provided to a user. This can be seen by copying your JWT Token from your localStorage (usually found within the formioToken variable) into jwt.io, which will give you the following.

{
  "user": {
    "_id": "5e5411ba1e29ee1aab5031d9"
  },
  "iss": "https://api.form.io:3000",
  "sub": "5e5411ba1e29ee1aab5031d9",
  "jti": "5fffbb5646d76c292a7b5df1",
  "iat": 1610595158,
  "exp": 1610609558
}

The jti property is the current user Session ID and will change if the user logs out and then logs in again.

Once a user logs out of their account, by hitting the logout API, the current session will become invalid, and all outstanding JWT tokens that use that session ID will no longer be able to be used. The user must first login again, which will re-establish a new session, and then that JWT token can be utilized to make API calls into the Form.io platform.

The Session Management system is used as an added security measure to enforce that JWT tokens cannot be used again once a user has logged out of their account. This also protects the situation where if any user is using multiple devices and logs out of one device, their session will also be logged out of all other devices.

Custom JWT Authentication

Any Deployed Enterprise customer can take advantage of this to establish their own custom authentication mechanisms by forging their own JWT tokens for their users.

There are many cases where Form.io needs to be tightly integrated into an existing platform with authentication mechanisms already established.

For these cases, Form.io can be utilized within deployed environments using JWT tokens that can be used as SSO into the Form.io platform. This does NOT require any user accounts within Form.io, but rather creates a way to pass along a dynamically generated JWT token claiming certain roles configured within the project.

Here is how it works.

  • To get started, you will need to deploy your own on-premise deployment into your environment and ensure you set the JWT_SECRET of that deployment to a secret only you know. Please see Docker Deployments for more information.

  • You will now need to create Roles within your Form.io project that you would like to use to control the access to certain Form.io operations. You can then assign those roles to the Permissions of the forms within the Access section of those forms. Please take note of the ID’s of these roles that were created since they will be used when generating the SSO tokens.

  • Now that you have some Roles created, you will then need to generate a special JWT token within your own backend platform, or within an authentication proxy using something like AWS Lambda. The payload for this token needs to be as follows.

{
  external: true,
  form: {
    _id: 'USER_RESOURCE_FORM_ID'
  },
  project: {
    _id: 'PROJECT_ID'
  }
  user: {
    _id: 'external',
    data: {
      name: 'joe'
    }
    roles: [
      'ROLE_ID_1',
      'ROLE_ID_2'
    ]
  }
}

You will then make the following replacements.

  • USER_RESOURCE_FORM_ID - This is the ID of the form/resource that would normally contain user records. NOTE: You do not need to provide a user record ID, but just the form ID.

  • PROJECT_ID - This is the Form.io project ID

  • ROLE_ID_1, ROLE_ID_2, … - These are the ID’s of the roles you would like this token to have when authenticating.

This token can be generated using a number of ways and should be generated within your own backend server or hosted lambda function. Since you know the JWT secret of the Docker deployment, you can generate valid tokens such as the following code.

/**
 * This code does require our Docker deployment where the "JWT_SECRET" would be the
 * same secret of the environment variable when the docker is spun up.
 */
var jwt = require('jsonwebtoken');
var token = jwt.sign({
  external: true,
  form: {
    _id: '59795d259be16e3ee58fddaa',
  },
  project: {
    _id: '59795d259be16e3ee58fdda6'
  }
  user: {
    _id: 'external',
    data: {
      name: 'joe'
    }
    roles: [
      '59795d259be16e3ee58fdda7'
    ]
  }
}, 'JWT_SECRET');
    
// We now have a token!
console.log(token);

This example uses Node.js and the JSON Web Token library, but this could be done within any backend server language. You will also need to make sure to replace JWT_TOKEN string with the password string you used for JWT_TOKEN when you deployed the server via. Docker.

  • Now that you have a token that is generated from the server, you will then send that to the client application when you serve the application. You can then place the following code in your Template to establish a SSO integration into Form.io.

<script type="text/javascript">
localStorage.setItem('formioToken', 'FORMIO_TOKEN');
</script>

Here you would just replace the FORMIO_TOKEN with the actual token generated from the server.

  • Now that you have a token added to localStorage using the special token formioToken, this will be used for all communication to the Form.io API platform and authenticate as the Roles provided in the token!

Last updated