Authentication
Every 8base workspace initializes with native support for signing up, managing, and authorizing your application's Users. This feature eliminates the requirement of managing emails and passwords or social sign-on providers without compromising on access to your user data.
Understanding Authentication
While 8base simplifies the process of setting up authentication, it's important to still have a basic understanding of what's going on under the hood! Using the diagram above, we can get a clear picture of how the authentication flow works.
1. Login / Signup
It's important to understand that 8base does not store or manage passwords. All login credentials get stored with an auth provider, which means that only an auth provider can login or register a user! Therefore, 8base allows you to use GraphQL Mutations or a Hosted Login Page to send a user's credentials to auth providers, However, it's the auth provider's system that will validate a user's credentials and identity.
2. Issuance
When an auth provider authenticates a user, it issues an idToken
. Think of the idToken
as a temporary passport that contains information about the user that authenticated. This idToken
get's returned to the front-end application. It can be stored and used to authenticate API requests made to an 8base workspace.
To dive deeper into idTokens (JSON Web Tokens), please visit https://jwt.io/
3. User Query
Simply authenticating a user doesn't add them to a workspace's Users table in 8base. However, using the idToken
, an authenticated request can be sent to the workspace API and check whether the token's user exists. To authenticate that request, the token only needs to be added as a Bearer token in the authorization header.
For example, if you're using JavaScript fetch
to handle the request, your script might look something like the following.
fetch('8BASE_WORKSPACE_API_ENDPOINT', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${idToken}`,
},
body: JSON.stringify({ query: '{ user { id } }' }),
})
.then(userExistsCallback)
.catch(userDoesntExistCallback);
4. Verification
When 8base receives an authorized API request (a request containing an idToken
) it validates that token with the issuing auth provider. 8base handles this step using data that's encoded in the idToken
. This way, a fraudulent token is detected and discarded before any sensitive data is accessed.
5. Validation
Once the auth provider validates that the idToken
– and the user claiming the token – are authentic, it let's 8base know. 8base now can confidently extract the user's primary identifier from the token (for example, email address) and query an existing record in the Users table.
6. Query Response
If the query runs successfully and a user record is returned, great! Just ensure to continue sending the idToken
in the authorization header on future API calls.
If the query fails and a user record is not found, we simply need to create a record for the new user. This can be accomplished using the userSignUpWithToken
, as seen below.
mutation {
userSignUpWithToken(
authProfileId: "8BASE_AUTHENTICATION_PROFILE_ID"
/* if not specified - will be executed with the
first auth profile in authenticationProfilesList */
user: {
email: "my@email.co"
// ...any other user data
}
) {
id
}
}
Authentication Types
Under the hood, 8base utilizes Amazon Cognito by default to manage your user's identities and ensure the best security standards. All user accounts are by default stored in an AWS account that's managed by 8base. For upgraded workspace plans, the option of connecting one's Auth0 account or an OpenID provider is available.
8base Authentication
To create an Authentication Profile, navigate to the App Services > Authentication
and press the +
button. The form that appears can be completed using the following fields.
Name: A name that describes what this profile does. In this sample case, you can replace My Auth in the screen shot above with a name like Guest User Auth.
Type: Select
8base Authentication
Self Signup:
Open to all
allows users to self-register. Otherwise, you can restrict access to only invited users (Off
) or users within a specific domain (Specific Email Domain Only
i.e., '@company.com').Roles: Roles can be either Guest, Administrator, or any custom role. Multiple-roles can be selected.
Client information
An authentication profile's corresponding client-side information is generated once created. Client-side information allows for connecting client applications to the 8base back-end and any corresponding authentication settings.
Client ID
and Domain
are not sensitive strings and are added to one or more client apps.
Login URL
is the auto-generated URL template leading to the Hosted Login Page. You should fill this with one of the Allowed Callbacks URLs
.
Configure Callback URLs
A callback URL is an endpoint that is invoked after a user authenticates. Users are not able to log into an application and will receive an error if this field is left empty. By default, the callback URL http://localhost:3000/auth/callback
is set. Keep it, or replace it with an existing URL from your application.
Configure Logout URLs
The logout URL is where a user is sent after logging out. Specify it in the Allowed Logout URLs field. The default logout URL is http://localhost:3000/logout
and attempting to log out when no logout URL was provided displays an error.
Your Own Auth0 Account
There are only a few steps required to set up your Auth0 account on 8base. First, navigate to the App Services > Authentication
of your workspace and create a new Authentication Profile. In the form that appears, select Your Auth0 Account.
All required information is in the settings of your Auth0 account.
OpenID Connect
The ability to set up an authentication provider that supports the OpenID specification is available for workspaces with a paid plan.
In the 8base Management Console, you're able to configure one or more authentication providers under App Services > Authentication
. Click the "+" button and fill out the provider form, select OpenID as the type and add an OpenID Provider URL. Once completed, the record is saved to your Authentication Profiles.
getToken Resolver
A custom getToken resolver mutation function must be deployed to the workspace. This can be done by installing the 8base CLI.
In the provided getToken function, the relevant environment variables are accessed - if set in the Management Console - to provide the required credentials and configurations. A request is then made to the authentication provider to query or create the authenticating user from the database and return the user's token.
functions:
getToken:
handler:
code: src/getToken.ts
type: resolver
schema: src/getToken.graphql
const { URLSearchParams } = require('url');
const fetch = require('node-fetch');
const gql = require('graphql-tag');
const jwtDecode = require('jwt-decode');
const APP_ID_CLIENT_ID = process.env.APP_ID_CLIENT_ID;
const APP_ID_TENANT_ID = process.env.APP_ID_TENANT_ID;
const APP_ID_SECRET = process.env.APP_ID_SECRET;
const APP_ID_URL = process.env.APP_ID_URL;
const TOKEN_PATH = '/token';
const CLIENT_REDIRECT_URI = process.env.CLIENT_REDIRECT_URI;
const CURRENT_USER_QUERY = gql`
query CurrentUser {
user {
id
email
}
}
`;
const USER_SIGN_UP_MUTATION = gql`
mutation UserSignUp($user: UserCreateInput!, $authProfileId: ID) {
userSignUpWithToken(user: $user, authProfileId: $authProfileId) {
id
email
}
}
`;
export default async (event: any, context: any) => {
const body = new URLSearchParams();
body.append('grant_type', 'authorization_code');
body.append('code', event.data.code);
body.append('client_id', APP_ID_CLIENT_ID);
body.append('redirect_uri', CLIENT_REDIRECT_URI);
let token;
let email;
try {
let tokenResponse = await fetch(`${APP_ID_URL}${APP_ID_TENANT_ID}/${TOKEN_PATH}`, {
body,
headers: {
'Authorization': 'Basic ' + Buffer.from(`${APP_ID_CLIENT_ID}:${APP_ID_SECRET}`).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded'
'Accept': 'application/json',
},
method: 'post',
});
({ id_token: token } = await tokenResponse.json());
try {
await context.api.gqlRequest(CURRENT_USER_QUERY, {}, {
authorization: token,
});
} catch (e) {
({ email } = jwtDecode(token));
await context.api.gqlRequest(USER_SIGN_UP_MUTATION, {
user: {
email,
},
authProfileId: event.data.authProfileId,
}, {
authorization: token,
});
}
} catch (e) {
console.log(e);
throw Error('Authorization Error');
}
return {
data: {
token,
},
};
};
type TokenResult {
token: String!
}
extend type Mutation {
getToken(code: String!, authProfileId: ID!): TokenResult
}
Setting Environment Variables
To set environment variables that can be accessed from within custom functions, open up your workspace, and navigate to Settings > Environment Variables
. Here, any key-value pair may be securely stored and accessed from within your functions at process.env.<ENV_VARIABLE_KEYNAME>
.
Troubleshooting
1: 'Not Authorized' error
If you're unable to get the authentication provider to work and are receiving a Not Authorized
error message, you may need to update the associated role and its API permissions. You can do this by first ensuring that the configured provider has an associated role, like Guest. Next, navigate to App Services > Roles > [ROLE_NAME] > Data
and ensure that the role is enabled for the Get Token function call.
2: Mismatch between auth provider user pool and 8base Users table
Make sure you keep Users table records in 8base up to date with records in your authentication provider user pool. Let's say you are using Custom Auth0 and have manually changed the email for some User record in 8base. This will lead to an authentication error because the email (primary identifier) from the Auth0 token and email from the existing User record are different.