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

# Sessions

> Managing sessions and authentication in Uplink

Sessions are the foundation of Uplink's security and connection model. Understanding how to create, manage, and secure sessions is essential for production use.

## What is a session?

A **session** is an authenticated connection that allows your JavaScript code to communicate with mobile devices. Sessions are created programmatically using your OAuth client credentials and enable secure, real-time bidirectional communication.

```typescript theme={null}
const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true }
  }
)
const client = await uplink.client.fromSession(session)
```

## Creating sessions

Sessions are created programmatically in your code using your OAuth client credentials. The session is automatically scoped to the project the client belongs to.

<Steps>
  <Step title="Get your credentials">
    Create an OAuth client in Uplink Console and copy its client ID and secret. See [Authentication](/fundamentals/authentication) for the full walkthrough.
  </Step>

  <Step title="Create a session in code">
    Use the `uplink.session()` method to create a session with your credentials:

    ```typescript theme={null}
    const session = await uplink.session(
      {
        clientId: process.env.UPLINK_CLIENT_ID,
        clientSecret: process.env.UPLINK_CLIENT_SECRET
      },
      {
        include: { ecdsa: true, ecdh: true }
      }
    )
    ```
  </Step>

  <Step title="Connect a client to the session">
    Create a client from your session to interact with connected devices:

    ```typescript theme={null}
    const client = await uplink.client.fromSession(session)
    ```
  </Step>

  <Step title="Deliver the session to your device">
    The `session` object exposes two URLs for connecting a device. Use the one that matches your integration:

    <Tabs>
      <Tab title="Connect app">
        Display `session.qrUrl` as a QR code in your UI. When your user scans it, the Uplink Connect app opens and joins the session automatically.

        ```typescript theme={null}
        import QRCode from 'qrcode'

        const qrCodeImage = await QRCode.toDataURL(session.qrUrl)
        // Render qrCodeImage in your UI
        ```
      </Tab>

      <Tab title="Native SDK">
        Deliver `session.sessionUrl` to your app through your own channel (push notification, deep link, API response, etc.), then pass it to the native SDK's `worker.connect` method.

        ```typescript theme={null}
        const sessionUrl = session.sessionUrl
        // Deliver sessionUrl to your iOS or Android app
        ```

        See the [iOS SDK](/api-reference/ios) and [Android SDK](/api-reference/android) guides for how the app side consumes the URL.
      </Tab>
    </Tabs>
  </Step>
</Steps>

## The session object

`uplink.session()` and [`uplink.getSession()`](/api-reference/client#uplinkgetsession) both
resolve to the same shape:

```typescript theme={null}
interface Session {
  sessionId: string
  sessionUrl: string
  qrUrl: string
  credential?: string
  keys?: {
    ecdh?: { public: string; private: string }
    ecdsa?: { public: string; private: string }
  }
}
```

Half of it is safe to hand out and half of it is credential material, so the distinction matters
as soon as you persist a session or return one from an HTTP endpoint:

| Field        | Sensitivity                 | What it is                                                                                                                                                         |
| ------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sessionId`  | Safe to store               | The session's identifier. Pass it to `getSession()` to look the session up later.                                                                                  |
| `sessionUrl` | Deliver to your device only | The URL a native SDK worker connects with. Send it to your own app through your own channel.                                                                       |
| `qrUrl`      | Safe to show a user         | The URL to render as a QR code for the Uplink Connect app.                                                                                                         |
| `credential` | **Secret**                  | The session's verifier. Anyone holding it plus a session URL can drive the session. Never return it to a browser. See [Session restriction](#session-restriction). |
| `keys`       | **Secret**                  | ECDH/ECDSA key pairs, present only when you asked for them with `include`. Each contains a `private` half — treat the whole object as secret.                      |

`credential` and `keys` are optional because neither is always present: `getSession()` never
returns `credential`, and `keys` only appears for the key types you requested via
`include: { ecdh: true, ecdsa: true }`.

<Warning>
  Do not return the whole session object from an API endpoint your frontend calls. Pick out
  `qrUrl` (and `sessionId` if your UI needs it) and leave `credential` and `keys` on the server.
</Warning>

## Session security

Sessions are authenticated with your OAuth client credentials, which provide:

* **Project identity**: Each OAuth client belongs to exactly one project — sessions created with it are scoped to that project automatically.
* **Secure communication**: Encrypted connections between your code and devices
* **Access control**: Manage permissions through Console settings

For how to create credentials and keep them safe, see [Authentication](/fundamentals/authentication).

### Session restriction

Every session is also bound to a secret **verifier**, so that holding a session's URL is not
enough to drive it. When you create a session, the SDK derives a challenge from the verifier and
sends only the challenge to Uplink — the verifier itself never leaves your process.

When a client connects, it presents the verifier as its `credential`. A client that presents the
matching credential can discover paired devices and open connections to them. A client that does
not still connects, but device discovery and connection setup are blocked for it.

You do not opt in to this. If you do not supply a verifier, `uplink.session()` generates one for
you, and either way it comes back on the session as `credential`:

```typescript theme={null}
const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true }
  }
)

console.log(session.credential)  // always present — generated for you here
```

Passing `restrict: { verifier }` does not turn restriction on. It only replaces the generated
value with one you chose, so you can reproduce it later:

```typescript theme={null}
const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true },
    restrict: { verifier: process.env.UPLINK_SESSION_VERIFIER }
  }
)
```

In the scripts above there is nothing to do: `uplink.client.fromSession()` reads `credential`
off the session object and passes it along for you. It only becomes something you manage when
you connect from a different runtime than the one that created the session.

### Reconnecting from another runtime

[`uplink.getSession()`](/api-reference/client#uplinkgetsession) refetches a session you created
earlier — a fresh `sessionUrl`, the `qrUrl`, and optionally the keys. It **never** returns
`credential`. Uplink stores only the challenge, so it cannot give the verifier back to you even
in principle.

That leaves two ways to reconnect, both of which come down to having the verifier on hand.

**Persist the credential you were given.** Store `session.credential` alongside
`session.sessionId` when you create the session, then merge it back in before connecting:

```typescript theme={null}
// First runtime — creating the session
const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true }
  }
)

await store.save({
  sessionId: session.sessionId,
  credential: session.credential
})
```

```typescript theme={null}
// Later runtime — reconnecting
const { sessionId, credential } = await store.load()

const session = await uplink.getSession(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  sessionId,
  { include: { ecdsa: true, ecdh: true } }
)

const client = await uplink.client.fromSession({ ...session, credential })
```

**Or choose your own verifier.** If you would rather not persist a generated value, supply the
verifier at create time and read it from the same place in both runtimes — then you only persist
the `sessionId`:

```typescript theme={null}
// Later runtime — reconnecting, having created the session with
// restrict: { verifier: process.env.UPLINK_SESSION_VERIFIER }
const { sessionId } = await store.load()

const session = await uplink.getSession(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  sessionId,
  { include: { ecdsa: true, ecdh: true } }
)

const client = await uplink.client.fromSession({
  ...session,
  credential: process.env.UPLINK_SESSION_VERIFIER
})
```

The verifier lives in your configuration, the same way your client secret does.

When choosing one:

* Treat it like a password. Anyone who has it, plus the session's URL, can drive the session.
* Use a high-entropy value — a UUID or a random 32-byte string — not a guessable name.
* Uplink cannot recover it for you. If you lose the verifier for a session you intended to
  reconnect to, create a new session.
* A verifier belongs to one session. Reusing one across sessions gains you nothing and widens
  the blast radius if it leaks.

<Note>
  Keep the verifier out of anything you show a user. `session.qrUrl` is the only URL a user ever
  needs — the credential is strictly between your code and Uplink.
</Note>

## Session lifecycle

### Connection

When you connect to a session, the client establishes a WebSocket connection to the Uplink relay server:

```typescript theme={null}
const session = await uplink.session(
  {
    clientId: process.env.UPLINK_CLIENT_ID,
    clientSecret: process.env.UPLINK_CLIENT_SECRET
  },
  {
    include: { ecdsa: true, ecdh: true }
  }
)
const client = await uplink.client.fromSession(session)
console.log('Connected to session')

// The client is now ready to interact with devices
```

### Active session

During an active session:

* Devices can connect and disconnect
* Browsers can be launched and managed
* Commands are sent in real-time
* Events are emitted for device state changes

```typescript theme={null}
client.on('worker-connected', (device) => {
  console.log('Device joined session:', device.address)
})

client.on('worker-disconnected', (device) => {
  console.log('Device left session:', device.address)
})
```

### Closing a session

Always close the client when you're done to properly clean up resources:

```typescript theme={null}
// Clean up in order: pages, browsers, then client
await page.close()
await browser.close()
await client.close()
```

<Tip>
  Create and destroy sessions as needed for your use case. For automated testing, create a new session for each test run and close it when complete.
</Tip>

### Session expiration

Sessions end when:

* The connection is closed by the client (`client.close()`)
* The session is terminated in the Console
* The OAuth client used to create the session is disabled or deleted
* Network connectivity is lost

<Warning>
  When a session expires, all connected devices are disconnected and browsers are closed. Plan for graceful handling of session expiration in long-running automations.
</Warning>

## Multi-session patterns

### Load distribution

For high-volume automation, distribute load across multiple sessions:

```typescript theme={null}
const sessions = await Promise.all([
  createAndConnectSession('session-1'),
  createAndConnectSession('session-2'),
  createAndConnectSession('session-3')
])

// Round-robin device allocation
const device = sessions[nextIndex++ % sessions.length]
```

## Next steps

<CardGroup cols={2}>
  <Card title="Device management" icon="mobile" href="/fundamentals/device-management">
    Learn how to manage devices in sessions
  </Card>

  <Card title="Client API" icon="plug" href="/api-reference/client">
    Explore the Client API reference
  </Card>
</CardGroup>
