# Write a custom sign-in script

With **Custom (code)**, the sign-in of an API is one JavaScript function that returns the headers every call needs. Use it when the forms do not fit: a login in two steps, a token issued by another host, a token request with extra parameters.

## The function

The gateway runs the function once for each connection and reuses the result.

```js filename="sign-in.js"
async function auth({ http, connection }) {
  const r = await http.post(`${connection.baseUrl}/auth/login`, {
    json: { email: connection.vars.USERNAME, password: connection.vars.PASSWORD },
  });
  if (!r.ok) throw new Error(`Login failed: ${r.status}`);
  return { headers: { Authorization: `Bearer ${r.body.token}` }, expiresIn: 3600 };
}
```

### `connection`

The connection the function runs for. It is read-only.

| Property | Value |
| --- | --- |
| `connection.baseUrl` | The connection's base URL. |
| `connection.name` | The connection's name. |
| `connection.labels` | The connection's labels, as an object. |
| `connection.vars` | The connection's variables, by name. A name the connection has no value for is `undefined`. |

You give a variable its value on each connection. Each one is secret (encrypted, write-only, masked in test output) or plain (visible and editable). The function reads both the same way.

### `http`

`http.get`, `http.post`, `http.put`, `http.patch` and `http.delete` take a URL and options. A relative URL is resolved against the connection's base URL.

| Option | Description |
| --- | --- |
| `headers` | Request headers. |
| `json` | Sent as JSON. Sets `Content-Type: application/json`. |
| `form` | Sent as `application/x-www-form-urlencoded`. |
| `body` | A string, sent as is. |
| `query` | Query parameters. |

Each call returns `{ status, ok, headers, cookies, body }`. `body` is parsed when the response is JSON. `cookies` holds the cookies the response set, by name.

`http` reaches the connection's host and the hosts listed in **Allowed hosts**, and nothing else. `fetch`, `require` and `process` are not available.

### The return value

| Property | Description |
| --- | --- |
| `headers` | Headers added to every call. For a gRPC API they are sent as metadata. |
| `query` | Query parameters added to every call. |
| `expiresIn` | Seconds until the gateway runs the function again. |
| `expiresAt` | The same as a moment: epoch milliseconds, epoch seconds or an ISO date. |

Return `headers`, `query` or both. Without `expiresIn` or `expiresAt`, the gateway keeps the result until a call answers `401`. With one, it runs the function again 30 seconds before the expiry, and also on a `401`.

## Limits

| Limit | Value |
| --- | --- |
| Requests in one run | 10 |
| Time for one run | 20 seconds |
| Time for one request | 15 seconds |
| Response body read | 1 MB |
| Redirects | Not followed. The function receives the `3xx` response. |

## Write and test the script

### Open the sign-in editor

1. In the portal, go to [**APIs**](https://auth.faustine.ai/go?to=%2Fintegrations%2Fapis) and select your API.
2. Select the **Authentication** tab, then **Edit**.
3. In **Sign-in method**, select **Custom (code)**.
4. Select **Save and write the sign-in**.

The sign-in editor opens with the function's signature fixed and its body editable. To begin from what the analysis detected, written as code, select **Start from the detected sign-in**.

### Allow the hosts the script calls

1. Select the **Variables** panel.
2. In **Allowed hosts**, enter each host the function calls other than the connection's own, one per line. `*.example.com` matches subdomains.

### Test it

1. Select the **Test** panel.
2. In **Connection**, select a connection. The function runs with that connection's stored variables. To test before any connection exists, select **No connection — type the values** and enter a base URL.
3. Enter a value for each variable the connection lacks. These values are used for this test only.
4. Select **Run the sign-in**.

The **Result** tab shows the headers and query parameters the function returned, with the credential values masked, and when the result is renewed. **Console** shows what the function wrote with `console.log`. **Requests** lists each `http` call with its status and duration. A test saves nothing.

### Save

Select **Save**. When the API signed in with another strategy before, the button reads **Save and use it**, and saving switches every connection of the API to the script.

## Give each connection its variables

The **Credentials** section of a connection lists the names the function reads, and the names other connections of the API hold, as rows to fill in. To add the values:

1. In the **Connections** tab, select the connection.
2. In **Credentials**, enter a value for each variable in **Variables**.
3. Set the lock on each row: locked values are encrypted and write-only, unlocked values stay visible.
4. Select **Save changes**.

A connection that lacks a value the function reads is marked **needs credentials**.

## Write it with Faustine AI

The **Faustine AI** panel of the sign-in editor reads your API's authentication code, tests a draft against the connection you selected and returns a proposal. The API keeps its sign-in until you apply the proposal in the editor and save.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| `login.example.com is not allowed — the script may call the connection's host and the hosts listed under "Allowed hosts"` | The function called a host that is not listed. | Add the host to **Allowed hosts** and save. |
| `A sign-in script may make at most 10 requests` | The function made more than 10 `http` calls in one run. | Reduce the calls. Return an expiry so the result is reused. |
| `The sign-in script returned no headers (nor query parameters) to send` | The function returned neither `headers` nor `query`. | Return at least one of them. |
| `Define the sign-in as: async function auth({ http, connection }) { … return { headers } }` | The script does not define a function named `auth`. | Keep the signature the editor provides. |

## Next steps

- [Authentication](https://docs.faustine.ai/apis/authentication/): the strategies that need no code.
- [Connections](https://docs.faustine.ai/apis/connections/): how connections store their variables.
- [The AI editor](https://docs.faustine.ai/apis/ai-editor/): the editor for the rest of the API's draft.
