> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firstwork.com/llms.txt
> Use this file to discover all available pages before exploring further.

# iFrame Embedding (Magic Link Login)

> Embed Firstwork in your app with secure JWT-based auto-login via iframe

## Overview

Embed the Firstwork hiring platform inside your own application using an `<iframe>` and a JWT-based magic login. Candidates land directly in their application without a second sign-in, while authentication stays secure because tokens are signed on your backend with a shared secret.

> \[!NOTE]
> If an applicant with the supplied `email` does not yet exist in the configured hiring flow, Firstwork **automatically creates the applicant and their application** using the identity claims in the JWT (`email`, `first_name`, `last_name`, `funnel_name`). If they already exist, their existing application is reused. Either way, they are logged in seamlessly inside the iframe — no separate sign-up step is required.

## How it works

```mermaid theme={null}
graph LR
    A[Partner Backend signs JWT] --> B[Frontend fetches token]
    B --> C[iframe loads magic-login URL]
    C --> D[Firstwork verifies JWT]
    D --> E[Applicant auto-logged in]
```

* Your backend signs a short-lived JWT containing the applicant's identity using a shared `SECRET_KEY` (provided by Firstwork).
* Your frontend fetches the token from your backend and sets the iframe `src` to `https://<company_name>.firstwork.com/magic-login/?token=<jwt>`.
* Firstwork verifies the JWT, creates or finds the applicant's application, and logs them in inside the iframe.

## Security principles

> \[!WARNING]
> **Never sign tokens in the browser.** The `SECRET_KEY` must remain server-side. Anyone with the secret can impersonate any applicant in your hiring flow.

* **JWT authentication**: JSON Web Tokens are signed with a shared secret using HS256.
* **Token generation on backend**: Tokens must be generated server-side, never in the frontend.
* **Short-lived tokens**: Always set an `exp` claim to limit exposure (e.g. 5 minutes).
* **iframe embedding**: Embed Firstwork only over HTTPS using the magic login URL.

## Step-by-step integration

### Get credentials from Firstwork

Obtain your `SECRET_KEY` and `hiring_flow_id` from Firstwork. Configure the funnel in the Firstwork UI and note its `funnel_name` (slug).

### Implement the backend token endpoint

Create a server-side endpoint that signs a JWT with your `SECRET_KEY` and returns it to the frontend. See the Node.js example below.

### Embed the iframe in your frontend

Fetch the token from your backend and set it as the `token` query parameter on the magic-login URL used as the iframe `src`.

### (Optional) Pass extra parameters

Append additional slug parameters (such as `workplace_id`) to the magic-login URL as query parameters to pre-configure the application during registration.

## Code examples

```javascript Backend (Node.js) theme={null}
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();

const SECRET_KEY = '<<provided-by-Firstwork>>';

app.get('/generate-firstwork-token', (req, res) => {
  const payload = {
    email: 'johndoe@gmail.com', // Populate dynamically
    first_name: 'John',
    last_name: 'Doe',
    hiring_flow_id: '<<provided-by-Firstwork>>',
    scope: 'LOGIN_FOR_APPLICANT',
    funnel_name: 'us-onboarding-flow',
    exp: Math.floor(Date.now() / 1000) + 5 * 60 // token valid for 5 minutes
  };

  const token = jwt.sign(payload, SECRET_KEY, { algorithm: 'HS256' });
  res.json({ token });
});

app.listen(3000, () => {
  console.log('Backend server running on port 3000');
});
```

```html Frontend (HTML + JS) theme={null}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Firstwork Iframe Integration</title>
    <style>
      body { font-family: Arial, sans-serif; padding: 20px; }
      iframe { border: 2px solid #333; width: 100%; height: 500px; }
    </style>
  </head>
  <body>
    <h1>Firstwork Embedded</h1>
    <iframe id="fw-iframe" title="Firstwork Portal"></iframe>

    <script>
      fetch('/generate-firstwork-token')
        .then(res => res.json())
        .then(data => {
          const iframeUrl = `https://<company_name>.firstwork.com/magic-login/?token=${data.token}`;
          document.getElementById('fw-iframe').src = iframeUrl;
        })
        .catch(err => console.error('Failed to load Firstwork:', err));
    </script>
  </body>
</html>
```

## JWT Payload

| Field            | Type          | Required    | Description                                                                                           |
| ---------------- | ------------- | ----------- | ----------------------------------------------------------------------------------------------------- |
| `email`          | string        | Yes         | Applicant's email address                                                                             |
| `first_name`     | string        | Yes         | Applicant's first name                                                                                |
| `last_name`      | string        | Yes         | Applicant's last name                                                                                 |
| `hiring_flow_id` | string (UUID) | Conditional | Hiring flow identifier provided by Firstwork. Required if `funnel_name` is not provided.              |
| `scope`          | string        | Yes         | Must be `LOGIN_FOR_APPLICANT`                                                                         |
| `funnel_name`    | string        | Conditional | Funnel slug for which the applicant will be registered. Required if `hiring_flow_id` is not provided. |
| `exp`            | integer       | Yes         | Expiration as a Unix timestamp. Keep short-lived (e.g. now + 5 minutes)                               |

JWTs must be signed with the `HS256` algorithm.

## Configuration

| Parameter     | Description                                                |
| ------------- | ---------------------------------------------------------- |
| `SECRET_KEY`  | A shared secret to sign JWT tokens. Provided by Firstwork. |
| Firstwork URL | `https://<company_name>.firstwork.com/magic-login/`        |

## Passing extra slug parameters

> \[!NOTE]
> To pass slug parameters for the application during registration, append them as query parameters to the iframe link:

```
https://<company>.firstwork.com/magic-login?token=<token>&workplace_id=<>&other_param=<>
```

## Best practices

1. **Never expose `SECRET_KEY` in the frontend code.**
2. Always set a short expiration (`exp`) in the JWT.
3. Only embed Firstwork domains via HTTPS.
4. Log JWT generation events for traceability.
5. Use HTTPS on your backend endpoint to protect token transmission.
