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

# Authentication

> How Sentry CLI authenticates with Sentry using OAuth device flow, token storage, and environment variables

Sentry CLI uses OAuth 2.0 device flow (RFC 8628) for secure authentication without requiring a browser redirect. Tokens are stored locally in SQLite with automatic refresh support.

## Authentication Methods

The CLI supports three authentication methods, checked in this priority order:

1. **`SENTRY_AUTH_TOKEN` environment variable** (highest priority)
2. **`SENTRY_TOKEN` environment variable** (legacy)
3. **OAuth tokens stored in SQLite** (lowest priority)

### Environment Variable Authentication

The simplest way to authenticate is via environment variables:

```bash theme={null}
export SENTRY_AUTH_TOKEN="your-token-here"
sentry issue list
```

Tokens from environment variables:

* Bypass all expiry and refresh logic
* Are never written to disk
* Take priority over stored OAuth tokens
* Empty or whitespace-only values are treated as unset

<Note>
  `SENTRY_AUTH_TOKEN` takes precedence over `SENTRY_TOKEN` to match the legacy sentry-cli behavior.
</Note>

### OAuth Device Flow

The recommended authentication method is OAuth device flow:

```bash theme={null}
sentry auth login
```

This initiates the device authorization flow:

1. CLI requests a device code from Sentry's `/oauth/device/code/` endpoint
2. CLI displays a user code and verification URL
3. User visits the URL and enters the code (or scans QR code)
4. CLI polls `/oauth/token/` endpoint until user completes authorization
5. Access token and refresh token are stored in SQLite

#### Device Flow Implementation

The device flow is implemented in `src/lib/oauth.ts`:

```typescript theme={null}
export async function performDeviceFlow(
  callbacks: DeviceFlowCallbacks,
  timeout = 600_000 // 10 minutes
): Promise<TokenResponse> {
  // Step 1: Request device code
  const {
    device_code,
    user_code,
    verification_uri,
    verification_uri_complete,
    interval,
  } = await requestDeviceCode();

  // Step 2: Show user code
  await callbacks.onUserCode(
    user_code,
    verification_uri,
    verification_uri_complete
  );

  // Step 3: Poll for token
  let pollInterval = interval;
  while (Date.now() < timeoutAt) {
    await sleep(pollInterval * 1000);
    const result = await attemptPoll(device_code);
    
    switch (result.status) {
      case "success":
        return result.token;
      case "slow_down":
        pollInterval += 5; // Increase interval if rate-limited
        continue;
    }
  }
}
```

#### OAuth Scopes

The CLI requests the following scopes:

* `project:read` - Read project data
* `project:write` - Write project data
* `org:read` - Read organization data
* `event:read` - Read events
* `event:write` - Write events
* `member:read` - Read organization members
* `team:read` - Read team data

## Token Storage

OAuth tokens are stored in SQLite at `~/.sentry/config.db`:

```sql theme={null}
CREATE TABLE auth (
  id INTEGER PRIMARY KEY,
  token TEXT,
  refresh_token TEXT,
  expires_at INTEGER,
  issued_at INTEGER,
  updated_at INTEGER
);
```

The auth table uses a single-row pattern (always `id = 1`) to store the active credential.

### Token Retrieval Priority

The `getAuthToken()` function checks sources in priority order:

```typescript theme={null}
export function getAuthToken(): string | undefined {
  // 1. Check environment variables first
  const envToken = getEnvToken();
  if (envToken) {
    return envToken.token;
  }

  // 2. Fall back to SQLite-stored token
  const db = getDatabase();
  const row = db.query("SELECT * FROM auth WHERE id = 1").get();
  
  if (!row?.token) {
    return undefined;
  }
  
  // Verify token hasn't expired
  if (row.expires_at && Date.now() > row.expires_at) {
    return undefined;
  }
  
  return row.token;
}
```

## Automatic Token Refresh

OAuth tokens are automatically refreshed when:

* Less than 10% of the token's lifetime remains (default threshold)
* A 401 Unauthorized response is received from the API
* Forced via `sentry auth refresh`

### Refresh Flow

The refresh flow is implemented in `src/lib/db/auth.ts`:

```typescript theme={null}
export async function refreshToken(
  options: RefreshTokenOptions = {}
): Promise<RefreshTokenResult> {
  // Environment tokens never expire
  const envToken = getEnvToken();
  if (envToken) {
    return { token: envToken.token, refreshed: false };
  }

  const row = db.query("SELECT * FROM auth WHERE id = 1").get();
  
  const remainingRatio = (expiresAt - now) / (expiresAt - issuedAt);
  
  // Refresh if less than 10% lifetime remains
  if (remainingRatio > REFRESH_THRESHOLD && now < expiresAt) {
    return { token: row.token, refreshed: false };
  }
  
  // Use refresh token to get new access token
  const tokenResponse = await refreshAccessToken(row.refresh_token);
  await setAuthToken(
    tokenResponse.access_token,
    tokenResponse.expires_in,
    tokenResponse.refresh_token
  );
  
  return {
    token: tokenResponse.access_token,
    refreshed: true,
    expiresIn: tokenResponse.expires_in
  };
}
```

<Info>
  Token refresh uses a singleton promise to prevent concurrent refresh attempts during parallel API calls.
</Info>

## Self-Hosted Sentry

For self-hosted Sentry instances, configure both URL and client ID:

```bash theme={null}
export SENTRY_URL="https://sentry.yourcompany.com"
export SENTRY_CLIENT_ID="your-oauth-client-id"
sentry auth login
```

### Creating an OAuth App

To use OAuth with self-hosted Sentry (requires 26.1.0+):

1. Navigate to Settings → Developer Settings in your Sentry instance
2. Create a new public OAuth application
3. Copy the Client ID
4. Set `SENTRY_CLIENT_ID` environment variable

<Warning>
  OAuth device flow requires Sentry 26.1.0 or later. For older versions, use `sentry auth login --token` with an API token instead.
</Warning>

### Configuration Resolution

The CLI reads configuration lazily (not at module load) to respect environment variables set after import:

```typescript theme={null}
function getSentryUrl(): string {
  return process.env.SENTRY_URL ?? "https://sentry.io";
}

function getClientId(): string {
  return process.env.SENTRY_CLIENT_ID ?? SENTRY_CLIENT_ID_BUILD;
}
```

This allows URL parsing from command arguments to set `SENTRY_URL` before the OAuth flow begins.

## Authentication State Commands

### Check Status

```bash theme={null}
sentry auth status
```

Shows the current authentication state and token source.

### Manual Refresh

```bash theme={null}
sentry auth refresh
```

Forces a token refresh even if the current token hasn't expired.

### Logout

```bash theme={null}
sentry auth logout
```

Clears stored tokens and related cached data:

* Auth tokens
* User info cache
* Organization region cache
* Pagination cursors

<Note>
  Logging out only clears SQLite-stored tokens. Environment variable tokens remain active.
</Note>

## Token Source Tracking

The CLI tracks where each token originated via the `AuthSource` type:

```typescript theme={null}
type AuthSource = 
  | "env:SENTRY_AUTH_TOKEN"
  | "env:SENTRY_TOKEN"
  | "oauth";

type AuthConfig = {
  token: string;
  refreshToken?: string;
  expiresAt?: number;
  issuedAt?: number;
  source: AuthSource;
};
```

This allows commands to conditionally skip refresh logic for environment tokens and provide accurate status messages.

## Error Handling

Authentication errors use the `AuthError` class with specific reason codes:

```typescript theme={null}
type AuthErrorReason = 
  | "not_authenticated"    // No token found
  | "expired"              // Token expired and can't refresh
  | "invalid";             // Token rejected by API

throw new AuthError(
  "expired",
  "Session expired. Run 'sentry auth login' to re-authenticate."
);
```

The CLI automatically triggers the login flow when `AuthError` is thrown, providing a seamless authentication experience.
