Helpers 

user model 

Gadget by default sets the following fields within your user model:

  • resetPasswordTokenExpiration
  • resetPasswordToken
  • emailVerificationTokenExpiration
  • emailVerificationToken
  • password
  • googleProfileId
  • googleImageUrl
  • emailVerified
  • email
  • lastName
  • firstName
  • lastSignedIn
  • roles

Google OAuth Sign Up trigger 

This trigger executes when a new user signs up using Google OAuth. The entire profile payload from Google is included in the trigger.

Google OAuth Sign In trigger 

This trigger executes when an existing user signs in using Google OAuth. The entire profile payload from Google is included in the trigger.

Email Password Sign Up trigger 

This trigger executes when a new user signs up using Email-Password authentication. This trigger exposes the signUp action to your API.

Email Password Sign In trigger 

This trigger executes when an existing user signs in using Email-Password authentication. This trigger exposes the signIn action to your API.

Verify Email trigger 

This trigger executes the verifyEmail action. It finds a user record by emailVerificationToken and checks emailVerificationTokenExpiration to see if the token is still valid. If it is the trigger sets emailVerified to be true.

Send Verify Email trigger 

This trigger executes the sendVerifyEmail action. When the sendVerifyEmail action is called from your API, this trigger finds a user record by email and checks that emailVerified is false. It generates a random code and provides it to the action in params.user.emailVerificationCode. The user record then has emailVerificationToken set to the SHA256 hash of this code.

Reset Password trigger 

This trigger executes the resetPassword action. It finds a user record by resetPasswordToken and checks resetPasswordTokenExpiration to see if the token is still valid. If it is the trigger sets the new password.

Send Reset Password trigger 

This trigger executes the sendResetPassword action. When the sendResetPassword action is called from your API, this trigger finds a user record by email. It then generates a random code and provides it to the action in params.user.resetPasswordCode. The user record then has resetPasswordToken set to the SHA256 hash of this code.

Change Password trigger 

This trigger executes the changePassword action. It checks that the currentPassword matches the user's current password and then sets the new password.

signUp action 

A user model's signUp action is a create action by default.

Its run function includes setting the lastSignedIn field of the user record to the current date and time. This indicates when the user last signed in or, in this context, when the user created their account.

In addition, it associates the current user record with the active session.

api/models/user/actions/signUp.js
JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers the form in web/routes/sign-up.tsx export const run: ActionRun = async ({ params, record, logger, api, session }) => { // Applies new 'email' and 'password' to the user record and saves to database applyParams(params, record); record.lastSignedIn = new Date(); await save(record); if (record.emailVerified) { // Assigns the signed-in user to the active session session?.set("user", { _link: record.id }); } return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, session, }) => { if (!record.emailVerified) { // Sends verification email by calling api/models/users/actions/sendVerifyEmail.ts await api.user.sendVerifyEmail({ email: record.email }); } }; export const options: ActionOptions = { actionType: "create", returnType: true, triggers: { googleOAuthSignUp: true, emailSignUp: true, }, };
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers the form in web/routes/sign-up.tsx export const run: ActionRun = async ({ params, record, logger, api, session }) => { // Applies new 'email' and 'password' to the user record and saves to database applyParams(params, record); record.lastSignedIn = new Date(); await save(record); if (record.emailVerified) { // Assigns the signed-in user to the active session session?.set("user", { _link: record.id }); } return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, session, }) => { if (!record.emailVerified) { // Sends verification email by calling api/models/users/actions/sendVerifyEmail.ts await api.user.sendVerifyEmail({ email: record.email }); } }; export const options: ActionOptions = { actionType: "create", returnType: true, triggers: { googleOAuthSignUp: true, emailSignUp: true, }, };

signIn action 

A user model's signIn action is an update action by default.

It updates the lastSignedIn field of the user record to the current date and time and associates the current user record with the active session.

api/models/user/actions/signIn.js
JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers form in web/routes/sign-in.tsx export const run: ActionRun = async ({ params, record, logger, api, session }) => { applyParams(params, record); record.lastSignedIn = new Date(); await save(record); // Assigns the signed-in user to the active session session?.set("user", { _link: record.id }); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, session, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "update", triggers: { googleOAuthSignIn: true, emailSignIn: true, }, };
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers form in web/routes/sign-in.tsx export const run: ActionRun = async ({ params, record, logger, api, session }) => { applyParams(params, record); record.lastSignedIn = new Date(); await save(record); // Assigns the signed-in user to the active session session?.set("user", { _link: record.id }); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, session, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "update", triggers: { googleOAuthSignIn: true, emailSignIn: true, }, };

signOut action 

A user model's signOut action is an update action by default.

It unsets the associated user on the active session, causing the session to be unauthenticated.

api/models/user/actions/signOut.js
JavaScript
import { ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ params, record, logger, api, session }) => { // Removes the user from the active session session?.set("user", null); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, session, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "update", triggers: { signOut: true, }, };
import { ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ params, record, logger, api, session }) => { // Removes the user from the active session session?.set("user", null); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, session, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "update", triggers: { signOut: true, }, };

verifyEmail and sendVerifyEmail actions 

A user model's verifyEmail action is a default action file upon app creation, that is designated to handle email verification scenarios for users, as determined by the Gadget developer.

api/models/user/actions/verifyEmail.js
JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers the sign up flow, this action is called from the email generated in /actions/sendVerifyEmail.ts export const run: ActionRun = async ({ params, record, logger, api }) => { // Applies new 'emailVerified' status to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { verifiedEmail: true, }, };
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers the sign up flow, this action is called from the email generated in /actions/sendVerifyEmail.ts export const run: ActionRun = async ({ params, record, logger, api }) => { // Applies new 'emailVerified' status to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { verifiedEmail: true, }, };

The sendVerifyEmail action, is a custom action that updates the user record with the provided parameters and then, if successful, it sends an email to the user containing a reset password link.

api/models/user/actions/sendVerifyEmail.js
JavaScript
import { applyParams, save, ActionOptions, DefaultEmailTemplates, Config, } from "gadget-server"; // Powers the sign up flow, this action is called from api/models/user/actions/signUp.ts export const run: ActionRun = async ({ params, record, logger, api, emails }) => { // Applies new hashed code 'emailVerificationToken' to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, emails, }) => { if ( !record.emailVerified && record.emailVerificationToken && params.user?.emailVerificationCode ) { // Generates link to reset password const url = new URL("/verify-email", Config.appUrl); url.searchParams.append("code", params.user?.emailVerificationCode); // Sends link to user await emails.sendMail({ to: record.email, subject: `Verify your email with ${Config.appName}`, html: DefaultEmailTemplates.renderVerifyEmailTemplate({ url: url.toString() }), }); } }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { sendVerificationEmail: true, }, };
import { applyParams, save, ActionOptions, DefaultEmailTemplates, Config, } from "gadget-server"; // Powers the sign up flow, this action is called from api/models/user/actions/signUp.ts export const run: ActionRun = async ({ params, record, logger, api, emails }) => { // Applies new hashed code 'emailVerificationToken' to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, emails, }) => { if ( !record.emailVerified && record.emailVerificationToken && params.user?.emailVerificationCode ) { // Generates link to reset password const url = new URL("/verify-email", Config.appUrl); url.searchParams.append("code", params.user?.emailVerificationCode); // Sends link to user await emails.sendMail({ to: record.email, subject: `Verify your email with ${Config.appName}`, html: DefaultEmailTemplates.renderVerifyEmailTemplate({ url: url.toString() }), }); } }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { sendVerificationEmail: true, }, };

resetPassword and sendResetPassword actions 

A user model's resetPassword action is a default action file upon app creation, that is designated to handle password reset scenarios for users, as determined by the Gadget developer.

api/models/user/actions/resetPassword.js
JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers form in web/routes/reset-password.tsx export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { // Applies new 'password' to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, connections, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { resetPassword: true, }, };
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers form in web/routes/reset-password.tsx export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { // Applies new 'password' to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, connections, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { resetPassword: true, }, };

The sendResetPassword action, is a custom action that updates the user record with the provided parameters and then, if successful, the action will send a verification email containing a unique link for the user to verify their email.

api/models/user/actions/sendResetPassword.js
JavaScript
import { applyParams, save, ActionOptions, DefaultEmailTemplates, Config, } from "gadget-server"; // Powers form in web/routes/forgot-password.tsx export const run: ActionRun = async ({ params, record, logger, api, emails }) => { // Applies new hashed code 'resetPasswordToken' to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, emails, }) => { if (record.resetPasswordToken && params.user?.resetPasswordCode) { // Generates link to reset password const url = new URL("/reset-password", Config.appUrl); url.searchParams.append("code", params.user?.resetPasswordCode); // Sends link to user await emails.sendMail({ to: record.email, subject: `Reset password request from ${Config.appName}`, html: DefaultEmailTemplates.renderResetPasswordTemplate({ url: url.toString(), }), }); } }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { sendResetPassword: true, }, };
import { applyParams, save, ActionOptions, DefaultEmailTemplates, Config, } from "gadget-server"; // Powers form in web/routes/forgot-password.tsx export const run: ActionRun = async ({ params, record, logger, api, emails }) => { // Applies new hashed code 'resetPasswordToken' to the user record and saves to database applyParams(params, record); await save(record); return { result: "ok", }; }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, emails, }) => { if (record.resetPasswordToken && params.user?.resetPasswordCode) { // Generates link to reset password const url = new URL("/reset-password", Config.appUrl); url.searchParams.append("code", params.user?.resetPasswordCode); // Sends link to user await emails.sendMail({ to: record.email, subject: `Reset password request from ${Config.appName}`, html: DefaultEmailTemplates.renderResetPasswordTemplate({ url: url.toString(), }), }); } }; export const options: ActionOptions = { actionType: "custom", returnType: true, triggers: { sendResetPassword: true, }, };

changePassword action 

A user model's changePassword action is a default action file upon app creation, that is designated to handle scenarios for users where they have to change/update their password, as determined by the Gadget developer.

api/models/user/actions/changePassword.js
JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers form in web/routes/change-password.tsx export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { // Applies new 'password' to the user record and saves to database applyParams(params, record); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, connections, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "update", triggers: { changePassword: true, }, };
import { applyParams, save, ActionOptions } from "gadget-server"; // Powers form in web/routes/change-password.tsx export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { // Applies new 'password' to the user record and saves to database applyParams(params, record); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, connections, }) => { // Your logic goes here }; export const options: ActionOptions = { actionType: "update", triggers: { changePassword: true, }, };

Using preventCrossUserDataAccess 

You can use the preventCrossUserDataAccess function, imported from the gadget-server package, to ensure that users can only access their data in your model's actions.

This function will automatically be added to any models you add. To opt out of this, simply remove the belongs to user field from your model, the preventCrossUserDataAccess function from your model actions, and the read permission filter.

An example of how to use this function in a custom blog model's create action:

api/models/blog/actions/create.js
JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; // add `preventCrossUserDataAccess` to the import statement import { preventCrossUserDataAccess } from "gadget-server/auth"; export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { applyParams(params, record); // add this line to prevent customers from accessing other customers' data await preventCrossUserDataAccess(params, record); await save(record); }; export const options: ActionOptions = { actionType: "create", };
import { applyParams, save, ActionOptions } from "gadget-server"; // add `preventCrossUserDataAccess` to the import statement import { preventCrossUserDataAccess } from "gadget-server/auth"; export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { applyParams(params, record); // add this line to prevent customers from accessing other customers' data await preventCrossUserDataAccess(params, record); await save(record); }; export const options: ActionOptions = { actionType: "create", };

Adding a tenancy filter 

Adding a tenancy filter to the access control configuration for your custom model will make sure that users can only access their own data. Note that we already add a filter on the read permission when you add a new model.

These filtered model permissions are written in Gelly, Gadget's data access language.

For example, this Gelly snippet could be used to filter a todo model data by customer. The todo model has a user relationship field that relates to the user model.

Example of a tenancy filter the todo model
gelly
filter ($user: User, $session: Session) on Todo [ where userId == $user.id ]

For more information on tenancy filters and filtered model permissions, see the access control guide.

session model 

All Gadget apps have a session model that is used to power session management. The current session is always available in Gadget actions and route handlers:

api/actions/someAction.js
JavaScript
export const run: ActionRun = async ({ params, record, logger, api, session }) => { logger.info({ session }, "the session associated with the current request"); };
export const run: ActionRun = async ({ params, record, logger, api, session }) => { logger.info({ session }, "the session associated with the current request"); };

csrfToken 

All session records have a special csrfToken property that can be used to prevent cross-site request forgery (CSRF) attacks.

Gadget automatically checks CSRF tokens for all form submission requests. To submit a form as an authenticated user you must include the CSRF token in the form data.

api/routes/GET-a-form.js
JavaScript
import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, api, logger, connections }) => { // send an HTML response with a form that has a CSRF token await reply.send(` <html> <body> <form method="POST" action="/submit-form"> <input type="hidden" name="csrfToken" value="${session.csrfToken}" /> <button type="submit">Submit</button> </form> </body> </html> `); }; export default route;
import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, api, logger, connections }) => { // send an HTML response with a form that has a CSRF token await reply.send(` <html> <body> <form method="POST" action="/submit-form"> <input type="hidden" name="csrfToken" value="${session.csrfToken}" /> <button type="submit">Submit</button> </form> </body> </html> `); }; export default route;

Custom auth state parameters 

You can add custom auth action parameters to the built in auth flow by:

  • Adding a custom parameter to the signup and/or signin actions:
api/models/user/actions/signUp or signIn
JavaScript
export const params = { example: { type: "string" }, };
export const params = { example: { type: "string" }, };
  • Adding the query parameter to the auth kickoff:
JavaScript
// In frontend code <a className="google-oauth-button" href={`/auth/google/start${search}?example=true`}> <img src="https://assets.gadget.dev/assets/default-app-assets/google.svg" width={22} height={22} />{" "} Continue with Google </a>;
// In frontend code <a className="google-oauth-button" href={`/auth/google/start${search}?example=true`}> <img src="https://assets.gadget.dev/assets/default-app-assets/google.svg" width={22} height={22} />{" "} Continue with Google </a>;
  • Pulling the value from the params action context:
api/models/user/actions/signUp or signIn
JavaScript
export const run: ActionRun = async ({ params, record, logger, api, session }) => { applyParams(params, record); const { example } = params; // ... };
export const run: ActionRun = async ({ params, record, logger, api, session }) => { applyParams(params, record); const { example } = params; // ... };

Hooks and components 

When working with Gadget authentication, there are several hooks and components from our @gadgetinc/react package that can help you manage the authentication state of your application.

The hooks use the Gadget client's suspense: true option, making it easier to manage the async nature of the hooks without having to deal with loading state.

HooksDescription
useSession()Retrieves the current user session within the app.
useUser()If a user is present in the session, it returns the current user; otherwise, it returns null for unauthenticated sessions.
useAuth()Returns an object representing the current authentication state of the session.
useSignOut()Returns a callback that you can call to sign out your current Gadget User from the current Session. This calls the configured signOutActionApiIdentifier action, which is the User signOut action by default.
ComponentsDescription
<SignedIn />Conditionally renders its children if the current session has a user associated with it, similar to the isSignedIn property of the useAuth() hook.
<SignedOut />Conditionally renders its children if the current session does not have a user associated with it.
<SignedInOrRedirect />Conditionally renders its children based on the user's sign-in status. If a user is currently signed in, it displays its children; otherwise, it redirects the browser using window.location.assign. This functionality is valuable for securing frontend routes.
<SignedOutOrRedirect />Conditionally renders its children when there is no user associated with the current session. However, if the user is signed in, it redirects the browser using window.location.assign. Its purpose is to facilitate redirection of frontend routes based on the user's sign-in status.

Was this page helpful?