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

# Issue and Verify Session Credentials

> Issue a single-use, time-bounded session credential, consume it, and confirm expiry, reuse, and revocation are all rejected.

<Info>**\[AVAILABLE]**, output below is a real run, this session, commit `651497a`.</Info>

## Goal

Confirm, by causing each failure yourself, that a session credential is genuinely single-use
and time-bounded, not just documented as such.

## Prerequisites

* Read [Credential isolation](/concepts/credential-isolation), this guide exercises
  `InMemorySessionCredentialVault` directly, the mechanism that page describes.

## Steps

This exact scenario already exists as `examples/tutorials/58-session-credentials/`, reuse it
rather than writing a parallel harness. It constructs a vault directly, no gateway, no
server, and puts a real credential through four cases: happy path, expiry, reuse, and
revocation.

```typescript theme={null}
// examples/tutorials/58-session-credentials/run.ts (excerpt)
const credentials = new InMemoryCredentialVault();
credentials.setCredential("oracle", { value: Object.freeze({ token: "oracle-secret-9876" }) });

const sessionCredentials = new InMemorySessionCredentialVault({
  credentials,
  clock: new SystemClock(),
  idGenerator: new RandomIdGenerator(),
  lifetimeMs: 1_000,  // short, so expiry is observable in a few seconds
});
```

Run it:

```bash theme={null}
node_modules/.bin/tsx examples/tutorials/58-session-credentials/run.ts
```

## Verify

Real output, this session:

```
Happy Path
--------------------------------------------------
Issued  : ae31c202-8e9d-4411-9aac-b3ece1f2151a
Consumed: token
✓ Consumed within the lifetime window.

Expiry
--------------------------------------------------
Waiting past the 1-second lifetime...
✓ Consumption after expiry rejected:
  Session credential has expired: f3ed26b0-80bc-44e9-b3ad-b7e126679e6a.

Reuse
--------------------------------------------------
✓ First consumption succeeded.
✓ Second consumption rejected:
  Session credential has already been used: a8c9cd67-ed37-4e15-ae18-dcce964c4758.

Revocation
--------------------------------------------------
✓ Session credential revoked before use.
✓ Consumption after revocation rejected:
  Session credential has been revoked: ff2a976c-e5dd-4f9c-ba94-0f2377e62fa2.
```

All three negative cases (expired, already-consumed, revoked) throw distinct, readable
errors naming the session credential ID, none of them silently succeed or return an empty
result, each is a real thrown `Error`.

## Next steps

* **Change `lifetimeMs`** to something longer or shorter and rerun, confirm the expiry
  window actually moves with it.
* **Call `consume()` before `issue()` resolves** (a nonexistent ID) and observe the error
  message names the ID, not a resolved credential value, matching the never-leaks guarantee
  in [Credential isolation](/concepts/credential-isolation).

## Troubleshoot

* **Expiry case doesn't reject.** `lifetimeMs` and the `sleep()` duration in the tutorial are
  both short (1 second, 1.1 second wait) to keep the tutorial fast, if you've modified either
  independently, they can drift out of sync.
* **You want this wired to a real connector, not called directly.** See [Add a connector with
  the Connector SDK](/guides/add-a-connector), and `examples/tutorials/59-secure-connectors/run.ts`
  for the full path: gateway attestation, a `SessionCredentialSecureConnector`, and
  destruction of the credential immediately after use.

## Next

<CardGroup cols={2}>
  <Card title="Add a connector with the Connector SDK" icon="plug" href="/guides/add-a-connector">
    Where a connector actually receives the credential this guide issued.
  </Card>

  <Card title="Gateway attestation" icon="stamp" href="/concepts/gateway-attestation">
    What has to succeed before a session credential can even be issued.
  </Card>
</CardGroup>
