Homepage

Authentication

Last edit: Sep 24, 2024

In order to create a useful web application most likely sooner than later a developer has to add authentication and authorization, to protect the privacy of their users. In this article we would like to describe the most common strategies to authenticate an user.

Authentication strategies using GraphQL

There are multiple ways how you can authenticatre a User in platformOS. All of them will leverage the same authenticate field which is implemented by the users GraphQL query:

query ($email: String!) {
  users(filter: { email: { value: $email } }, per_page: 1) {
    results {
      id
      email
      authenticate {
        // your strategy
      }
    }
  }
}

The query filters the users by email (which is unique, so there will be either 0 or 1 result), and then it uses the authenticate GraphQL field, which accepts various strategies for authentication. We are describing them now

password

The most common one is password - it is a field that accepts one argument - user's password. Behind the scenes it will be hashed using bcrypt2 password hashing function and compared to the value that is stored in the database. It will return true if there is a match and false otherwise. Example query for using password field:

query ($email: String!, $password: String!) {
  users(filter: { email: { value: $email } }, per_page: 1) {
    results {
      id
      email
      authenticate {
        password(password: $password)
      }
    }
  }
}

The password can be set during registration, which can be implemented using user_create mutation, for example:

mutation {
  user_create(user: { email: "user@example.com" password: "secret" }) {
    id
  }
}

and can also be later updated using user_update mutation, for example:

mutation ($id: $ID!){
  user_update(id: $id, user: { password: "secret2" }){
    id
  }
}

Please refer to the User Authentication tutorial to see a step-by-step guide.

temporary_token

Temporary token can be used to authenticate the user without the necessity of providing a password. One of the common example is in the reset password functionality, when you generate a temporary token and send it to the user's email. The temporary_token accepts one argument - token, which can be generated also using users GraphQL query via a temporary_token field - it accepts expires_in argument, that you can use to limit the lifespan of a token.

To generate the temporary token which expires after 48 hours, use the following GraphQL query:

query ($email: String!, $expires_in: Float = 48) {
  users(filter: { email: { value: $email } }, per_page: 1) {
    results {
      id
      email
      temporary_token(expires_in: $expires_in)
    }
  }
}

To verify if the token is valid:

query ($email: String!, $token: String!){
  users(filter: { email: { value: $email }}, per_page: 1) {
    results {
      id
      email
      authenticate {
        temporary_token(token: $token)
      }
    }
  }
}

If the temporary_token is still valid and matches user's email, results.authenticate.temporary_token will be true, otherwise it will be false.

JWT

Alternatively, you can implement authentication with JWT. To obtain the JWT for the first time, you should use one of the authentication strategies described above and if successful, return using jwt_token (yes, JSON Web Token Token :) ). For example, if you want to use password as your authentication strategy to obtain JWT, you can achieve it with the following query:

query ($email: String!, $password: String!) {
  users(filter: { email: { value: $email } }, per_page: 1) {
    results {
      id
      authenticate {
        password(password: $password)
      }
      jwt_token(algorithm: HS256)
    }
  }
}

You can then authenticate user using JWT strategy by using jwt field:

query ($email: String!, $jwt: String!) {
  users(filter: { email: { value: $email } }, per_page: 1) {
    results {
      id
      authenticate {
        jwt(token: $jwt)
      }
    }
  }
}

However, the main purpose of using JWT would be to decode it and obtain user data associated with it. You can do it using jwt_decode_and_set_session mutation:

  jwt_decode_and_set_session(jwt_token: $token) {
    email
    first_name
    last_name
    jwt_token
    id
  }
  }

Altohugh JWT is meant to be used instead of a session based authentication, it is called this as a utility function to make current_user working seamlessly.

If you need more control over JWT, instead of using built-in GraphQL, you can use jwt_encode jwt_decode Liquid Filters

Please refer to JWT Tutorial to see example usage of JWT in platformOS.

otp_code - 2FA

Adding 2FA to platformOS application is easy thanks to the built in One Time Password (OTP). Adding 2FA consists of two steps.

First, you have to display autogenerated OTP secret to the user, who wants to enable 2FA, so they can configure their OTP client like Authy, Google Authenticator etc:

query ($email: String!, $expires_in: Float = 48) {
  users(filter: { email: { value: $email }}, per_page: 1) {
    results {
      id
      email
      otp {
        secret_as_svg_qr_code(issuer: "Example Co.")
        secret
        current_code
      }
    }
  }
}

The query returns secret_as_svg_qr_code (check arguments for this field if you would like to customize it) so you can display a QR code on the website that User can scan to easily configure their OTP client. Some users might not want to scan a QR code, that's why we provide a raw secret, that they can type manually in their OTP client. Lastly, the current_code contains the actual OTP that needs to be verified. This is how you can ensure that the user has configured their OTP client correctly.

In order to verify if the code is valid, you can use otp_code field that takes two arguments - code, which is the actual code entered by the user, and drift.

query auth($email: String!){
  users(per_page: 20,
    filter: { email: {
      value: $email
    }}
  ) {
    results {
      email
      id
      authenticate {
        otp_code(code: $token, drift: 30)
      }
    }
  }
}

Note that for example you can combine the password verification together with OTP verification:

query ($email: String!, $password: String!) {
  users(filter: { email: { value: $email } }, per_page: 1) {
    results {
      email
      id
      authenticate {
        password(password: $password)
        otp_code(code: $token, drift: 30)
      }
    }
  }
}

Signing user in

After successful authentication, there are two most common paths you want to take - create a session for the authenticated user or obtain user's JWT.

In order to generate a session that is connected to the authenticated user, you will want to use sign_in Liquid Tag. It takes two arguments - the user_id that has been authenticated based on the strategy described above, and optional timeout_in_minutes which specifies when the session will expire and user will be asked to authenticate again.

Accessing current user

After invoking the sign_in tag, you will be able to access current user's data using either GraphQL's current_user GraphQL query or in Liquid using context.current_user, usually in combination with an explicit users GraphQL Query.

Signing user out

Manual Log Out

Allowing the user to log out is a crucial security requirement of any web application that offers signing user in. You can implement this functionality by invoking user_session_destroy mutation:

mutation {
  user_session_destroy
}

Identity Providers

We recommend to integrate with identity provider by explicitly triggering Api Calls. We hope that more and more community maintained integrations will be provided out of the box as part of the pos-module-user. In the meantime however, you can still use our Built-in integration with identity providers. Please note that it does limit your flexibility, however for the typical use cases should be sufficient.

Note

To learn about the basic usage to implement authentication, please follow Getting Started with User Authentication article.
There is also a pos-module-user which allows you to easily add authentication and authorization to your application.

Questions?

We are always happy to help with any questions you may have.

contact us