blade.im
289

Auth

Blade natively offers the blade/auth package, which provides utility functions for easily adding different authentication methods to your application, without having to maintain them.

The functionality of the package is built around Triggers, which make it possible to capture Blade queries and run custom logic.

Setup

To enable all default authentication features, create the following files in a directory named triggers at the root of your application.

Additionally, a BLADE_AUTH_SECRET environment variable should be present in production (not necessary during development because a default value is provided there), which must contain the secret used to sign authentication tokens. You can run openssl rand -base64 30 to generate a new secret that you can store in the environment variable.

// triggers/account.ts

import { getAccountTriggers } from 'blade/auth/triggers';
export default getAccountTriggers();
// triggers/session.ts

import { getSessionTriggers } from 'blade/auth/triggers';
export default getSessionTriggers();

The following models must be added as well:

// schema/index.ts

export { Account, Session } from 'blade/auth/models';

Config

To customize the behavior of the authentication logic and handle actions such as sending emails, you can provide configuration options to the function call in the respective trigger file:

// triggers/account.ts

import { getAccountTriggers } from 'blade/auth/triggers';

export default getAccountTriggers({
  sendEmail: async ({ type, account, token, options }) => {
    if (type === "ACCOUNT_CREATION") {}
    if (type === "PASSWORD_RESET") {}
  }
});

If needed, you can also define additional triggers that would be run after the auth triggers:

// triggers/account.ts

import { triggers } from 'blade/schema';
import { getAccountTriggers } from 'blade/auth/triggers';
import type { Account } from 'blade/types';

const authTriggers = getAccountTriggers(...);

export default triggers<Account>(authTriggers, {
  // Define additional triggers for the Account model here.
  add: () => {}
});

Status

You can access the currently active account using the query below.

This is possible during signup (based on the account cookie that gets set during signup) and while the account is logged in (based on the session cookie that gets set during login).

const account = use.account();

To access the current session instead (which includes details such as the device that was used to log in), you can use the query below. Note that a session only exists after logging in.

const session = use.session();

If the user is logged in and you want to access both the session and the account, you can do so by resolving the account link field of the session as shown below.

const session = use.session.using(['account']);

// ^ { browser: 'chrome', account: { email: '...' } }

Actions

To trigger specific actions, you can then run one of the following queries with the useMutation hook on the client-side, depending on what actions the user performs.

In addition to the actions documented below, you can, of course, also run any other Query for the models that blade/auth provides, such as Account and Session. For example, you might choose to count records or filter them in a specific way.

You can choose between either running a Query or using Blade's Form component.

Signing Up

This will send the user an email using the sendEmail function you've defined. That email should link them to a page for the Verifying Email step.

This query will automatically set a account cookie with the ID of the newly created account. The login performed in Verifying Email automatically removes this cookie.

Query

await add.account.with({
    email: 'elaine@thorne.co',
    password: '1234'
});

Form

<Form model="account">
  <Input type="email" name="email" placeholder="Email" />
  <Input type="password" name="password" placeholder="Password" />

  <button>Sign Up</button>
</Form>

Verifying Email

After clicking the link in the signup email, the user should be taken to a page that can show some kind of a "Congratulations" message, with a button for continuing to the logged-in area of the app.

This button should trigger the following query, which validates the email verification token and logs the user in by creating a session.

Query

const { searchParams } = useLocation();
const token = searchParams.get('token');

await add.session.with.account({ emailVerificationToken: token });

Form

const { searchParams } = useLocation();
const token = searchParams.get('token');

<Form model="session">
  <Input type="hidden" name="account.emailVerificationToken" value={token} />
  <button>Get Started</button>
</Form>

Resending Verification

If a user lost the verification email that you sent them during signup and is therefore stuck, you can send another verification email to make sure they can complete their signup and join your application.

Query

await set.account.to({ emailVerificationSentAt: new Date() });

Form

<Form model="account" set>
  <Input type="hidden" name="emailVerificationSentAt" value={new Date()} />
  <button>Resend Email</button>
</Form>

Logging In

If the user would like to manually log in using their email address and password, you can make that possible using the query below.

This query will automatically set a session cookie.

Query

await add.session.with.account({
    email: 'elaine@thorne.co',
    password: '1234'
});

Form

<Form model="session">
  <Input type="email" name="account.email" placeholder="Email" />
  <Input type="password" name="account.password" placeholder="Password" />

  <button>Log In</button>
</Form>

Logging Out

This query will automatically revoke the session cookie.

Query

await remove.session();

Form

<Form model="session" remove>
  <button>Log Out</button>
</Form>

Forgot Password

Used when the user forgot their password and therefore cannot log in.

Query

await set.account({
  with: { email: 'elaine@thorne.co' },
  to: { password: null }
});

Form

<Form model="account">
  <Input type="email" name="email" placeholder="Email" target />
  <Input type="hidden" name="password" value={null} />

  <button>Send Confirmation</button>
</Form>

Reset Password

Can be shown after the user clicked the link in the email that was sent by Forgot Password.

Query

const { searchParams } = useLocation();
const emailVerificationToken = searchParams.get('token');

await set.account({
  with: { emailVerificationToken },
  to: { password: 'my-new-password' }
});

Form

const { searchParams } = useLocation();
const emailVerificationToken = searchParams.get('token');
const account = use.account.with({ emailVerificationToken });

{account ? (
  <Form model="account" target={{ emailVerificationToken }}>

    {/* Tells password managers which account to update. */}
    <Input type="hidden" name="email" value={account.email} />
    <Input type="password" name="password" placeholder="New Password" />

    <button>Change Password</button>
  </Form>
) : (
  <span>Your link has expired</span>
)}

Canceling Signup

If a user changes their mind and wants to reverse their account creation before verifying their email address (which might happen because they realized that the email address they entered was wrong, for example), you can let them cancel the signup and thereby clean up their unverified account.

The same query can be used to delete the currently logged-in account.

Query

await remove.account();

Form

<Form model="account" remove>
  <button>Delete Account</button>
</Form>

Utility Functions

Blade also re-exports a few helpful utility functions for authentication:

getSessionCookie

This function can be used to parse the current session cookie inside a trigger.

For example, you could place the trigger below inside the trigger file of a model for which you would like to log a message every time a record is added.

import { triggers } from 'blade/schema';
import { getSessionCookie } from 'blade/auth/utils';
import type { Session } from 'blade/types';

export default triggers<Session>({
  followingAdd: async ({ cookies }) => {
    const { accountId } = await getSessionCookie(cookies);
    console.log(`A new record was added by ${accountId}`);
  }
});