# Workflows  ## Protecting HTTP routes  Your app's [HTTP routes](https://docs.gadget.dev/guides/http-routes) can be protected using the `preValidation` function you can restrict access to signed-in users only. ```typescript import { preValidation, RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ reply }) => { await reply.send("this is a protected route!"); }; route.options = { preValidation, }; export default route; ``` This route will return `403 Forbidden` if accessed without signing in, and will run the route handler if accessed by someone who is signed in. ## Protecting pages (frontend routes)  Routes in your app's frontend can be protected using two [Gadget helper components, `SignedInOrRedirect` and `SignedOutOrRedirect`](https://docs.gadget.dev/guides/plugins/authentication/helpers#hooks-and-components). These components conditionally render their children based on the user's sign-in status and handle redirection to secure frontend routes. Both components use the window.location.assign method to redirect the browser when necessary. Let's take a look at an example below using both in tandem: ```tsx export const SomePage = () => ( }> {/* This route will be accessible only if the user is signed out */} } /> {/* This route will be accessible only if the user is signed in */} } /> ); ``` ## Environment-signed JWTs  You can use a `GADGET_ENVIRONMENT_JWT_SIGNING_KEY` environment variable to sign JWTs that authenticate requests to your Gadget app. When a JWT is signed with this key and used as an `Authorization: Bearer` token, Gadget validates the token and establishes the appropriate session, enabling proper access control and session management. ### Setting up the signing key  First, create an environment variable in your Gadget app with the identifier `GADGET_ENVIRONMENT_JWT_SIGNING_KEY`. This variable should be marked as a secret and contain a secure random string that will be used to sign your JWTs. ### Signing JWTs  When signing a JWT with `GADGET_ENVIRONMENT_JWT_SIGNING_KEY`, you must include the following required claims: * **`aud` (audience)**: The primary domain host of your environment. For example: `my-app.gadget.app` * **`sub` (subject)**: The `session` record ID that you want to authenticate You can also include standard JWT claims like `iat` (issued at) and `exp` (expiration) to control the token's validity period. Here's an example of signing a JWT in an action: ```typescript import { ActionRun, Config } from "gadget-server"; import jwt from "jsonwebtoken"; export const run: ActionRun = async ({ api, logger, params }) => { // Get the signing key from environment variables const signingKey = process.env["GADGET_ENVIRONMENT_JWT_SIGNING_KEY"]; if (!signingKey) { throw new Error("GADGET_ENVIRONMENT_JWT_SIGNING_KEY not configured"); } // Find or create a "shopper" user based on your authentication logic // The upsert method will find an existing shopper by email, or create one if it doesn't exist const shopper = await api.shopper.upsert( { email: params.email, // ... other user fields // Match on the email field to determine if the record exists on: ["email"], }, { select: { id: true, session: { id: true, }, }, } ); // Get or create a session for the user let sessionRecord = shopper.session; if (!shopper.session || !shopper.session.id) { // Create a new session linked to the user sessionRecord = await api.internal.session.create({ shopper: { _link: shopper.id }, // Optionally set roles for the session roles: ["authenticated"], }); } // Sign the JWT with the session ID const jwtToken = jwt.sign( { aud: Config.primaryDomain, sub: sessionRecord.id, // ... other claims like iat, exp, etc. }, signingKey ); return { token: jwtToken }; }; ``` ### Configuring the Gadget API client  Instead of manually adding the `Authorization` header to each request, you can configure your Gadget API client to automatically include the JWT token in all requests. Use the `custom` authentication mode: ```typescript import { ExampleAppClient } from "@gadget-client/example-app"; // fetch the jwt token from the server first const jwtToken = "your-signed-jwt-token"; const api = new ExampleAppClient({ authenticationMode: { custom: { processFetch: async (_input, init) => { init.headers ??= {}; ( init.headers as Record ).authorization = `Bearer ${jwtToken}`; }, processTransactionConnectionParams: async (params) => { // For websocket connections used in transactions params.auth = { type: "custom", token: jwtToken }; }, }, }, }); // Now all API calls will automatically include the JWT token const session = await api.currentSession.get(); ``` When Gadget receives a request with a Bearer token signed with `GADGET_ENVIRONMENT_JWT_SIGNING_KEY`, it: 1. Verifies the JWT signature using the signing key 2. Validates that the `aud` claim matches the environment's primary domain 3. Looks up the session using the `sub` claim (session ID) 4. Establishes the session on the request, enabling access control and session-based features This allows you to create custom authentication flows, like in Shopify Shop Mini apps, while leveraging Gadget's built-in session management and access control system. #### Using the JWT to make manual requests  You can also use the JWT to authenticate manual calls to your Gadget API by including it as a `Bearer` token in the `Authorization` header: ```typescript const response = await fetch("https://my-app.gadget.app/api/graphql", { method: "POST", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ query: "{ currentSession { id } }", }), }); ``` Access control is not applied to your HTTP routes when you are using the JWT to authenticate requests. You need to manually verify the JWT or use the .