# 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 .