# Credentials

> For the complete documentation index, see [llms.txt](https://effectkit.dev/llms.txt).

Authenticate with fixed secrets or short-lived tokens, and rotate them without restarting.

Source: https://effectkit.dev/docs/redis/credentials

Credentials are `Redacted` values, never plain strings. Pass a fixed value for a static password, or an `Effect` that fetches one when a connection opens.

## Using a fixed password [#using-a-fixed-password]

Put the secret in a `Redacted` and read it from `Config`, so it never appears in logs.

**Example** (Reading a Password from the Environment)

```ts twoslash
import { Config } from "effect";
import { RedisNode } from "@effect-kit/redis/node";

export const RedisLive = RedisNode.layerConfig(
  Config.all({
    url: Config.String("REDIS_URL"),
    credentials: Config.all({
      username: Config.Redacted("REDIS_USERNAME"),
      password: Config.Redacted("REDIS_PASSWORD"),
    }),
  }),
);
```

`username` is optional. A password in the URL (`rediss://user:pass@host`) also works, but giving both a URL password and `credentials` fails with `RedisConfigError`. A plain string for `credentials` is a type error.

## Fetching credentials on demand [#fetching-credentials-on-demand]

Pass an `Effect` that returns the values. It runs for every new connection, and may require services, which become requirements of the layer.

**Example** (A Token Source That the Layer Requires)

```ts twoslash
import { Context, Effect } from "effect";
import type { Layer } from "effect";
import { RedisCredentials } from "@effect-kit/redis";
import type { CredentialValues } from "@effect-kit/redis";
import { RedisNode } from "@effect-kit/redis/node";

class TokenSource extends Context.Service<
  TokenSource,
  { readonly next: Effect.Effect<CredentialValues> }
>()("app/TokenSource") {}

const acquire = Effect.gen(function* () {
  const source = yield* TokenSource;
  return yield* source.next;
});

export const RedisLive = RedisNode.layer({
  url: "rediss://cache.example.com:6380",
  credentials: RedisCredentials.provider(acquire, { refreshBeforeMillis: 120_000 }),
});

type Needs = Layer.Services<typeof RedisLive>;
//   ^?
```

`RedisCredentials.provider(effect, policy)` wraps the effect with a cache. The values it returns have `password`, optional `username`, and optional `expiresAtMillis`: an absolute UTC time in epoch milliseconds, as a number.

| Option                     | Default | Meaning                                                  |
| -------------------------- | ------- | -------------------------------------------------------- |
| `refreshBeforeMillis`      | `60000` | How long before `expiresAtMillis` a new token is fetched |
| `acquisitionTimeoutMillis` | `5000`  | How long one fetch can take (1 to 60000)                 |
| `maxWaiters`               | `128`   | How many callers can wait for a fetch (1 to 10000)       |

An `Effect` passed directly as `credentials` is the same as `RedisCredentials.provider(effect)` with these defaults. If the effect fails or times out, opening the connection fails with `HandshakeError` where `stage` is `"credentials"`.

## Retiring connections before a token expires [#retiring-connections-before-a-token-expires]

A connection authenticates once, when it opens. `credentialLifetime` decides when an open connection is closed so the next command reopens it with fresh credentials.

| `mode`           | Behavior                                                                                                                             |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `"reconnect"`    | The default. Connections stay open until something else closes them.                                                                 |
| `"token-expiry"` | Closes the connection `retireBeforeMillis` (default `30000`) before `expiresAtMillis`. The credentials must carry `expiresAtMillis`. |
| `"max-age"`      | Closes the connection `maxAgeMillis` after it opened.                                                                                |

**Example** (Closing Connections Before the Token Expires)

```ts
import { Effect, Redacted } from "effect";
import { RedisCredentials } from "@effect-kit/redis";
import { RedisNode } from "@effect-kit/redis/node";

const fetchToken = Effect.sync(() => ({
  username: Redacted.make("app"),
  password: Redacted.make("short-lived-token"),
  expiresAtMillis: Date.now() + 15 * 60 * 1000,
}));

export const RedisLive = RedisNode.layer({
  url: "rediss://cache.example.com:6380",
  credentials: RedisCredentials.provider(fetchToken),
  credentialLifetime: { mode: "token-expiry", retireBeforeMillis: 30_000 },
});
```

The package does not insert `AUTH` into an open connection. It closes the connection and lets the next one authenticate. A cached token is also refreshed at least `retireBeforeMillis` early in this mode.

## Rotating on demand [#rotating-on-demand]

`RedisCredentials` is a service the runtime layer provides. Its `rotate` effect discards the cached credentials and closes the open connections. New connections fetch fresh credentials.

**Example** (Rotating After a Secret Changes)

```ts twoslash
import { Effect } from "effect";
import { RedisCredentials } from "@effect-kit/redis";

export const rotate = Effect.gen(function* () {
  const credentials = yield* RedisCredentials;
  yield* credentials.rotate;
});
```

On Upstash, `rotate` aborts in-flight HTTP requests instead of closing connections. `rotate` fails with `CredentialError`, whose `reason` is one of `configuration`, `acquisition`, `expired`, `timeout`, `capacity`, `closed`, `rotated` or `retirement`.

Credential errors never include the secret or the provider response.
