# Handle Slack OAuth access_denied when the user declines the install

## TL;DR
access_denied is not a bug: the user clicked Deny on the Slack authorization screen. Treat it as a user decision, not a retryable error. Show a clear page explaining what the app needs and why, offer a single deliberate way to restart the install, and never auto-retry the OAuth redirect in a loop. If denials are frequent, the consent ask is too broad or too poorly explained.

## The error
```text
OAuth callback receives: ?error=access_denied&state=[state value]
Slack did not issue a code; the user denied the authorization request
```

## Use this skill
Agent? Search Vectle for this error:
```bash
curl -s "https://vectle.com/api/v1/search?q=slack%20oauth%20access_denied%20user%20denied%20app%20install"
```

## Fix it

### Step 1: Branch on access_denied in the callback handler

```bash
In the OAuth callback, check for error=access_denied before attempting the code exchange. Route it to a dedicated "install declined" page, not the generic error handler.
```

Expected: no exception, no token exchange attempt, no retry. The denial is a first-class outcome.

### Step 2: Explain the ask in plain language

```bash
On the declined page, list the requested scopes in human terms: what each permission does and why the onboarding flow needs it.
```

Expected: the user understands the request. Most denials come from a consent screen that reads like a legal document.

### Step 3: Offer one deliberate retry path

```bash
Provide a single "Try again" button that restarts the authorize URL flow with a fresh state value.
```

Expected: the user can re-consent on purpose. One button, no auto-redirect loops.

### Step 4: Log denials separately from errors

```bash
Record the denial with user id, timestamp, and scopes requested, in a log stream distinct from exception logs.
```

Expected: denial rate is visible in dashboards instead of buried in error noise. Support can see patterns.

### Step 5: Shrink the ask if denials are frequent

```bash
If the denial rate is high, cut requested scopes to the minimum the flow needs and rewrite the app's description on the consent screen.
```

Expected: denial rate drops. Fewer, better-explained scopes convert better.

## When this applies

- The OAuth callback arrives with error=access_denied
- Users report the install "did nothing" after the Slack screen
- Denial rates spike after a scope change

## When it doesn't

- The error is invalid scopes or redirect_uri_mismatch (those fail before consent)
- No callback arrives at all (network or redirect issue)
- The user approved but the code exchange failed (different bug)

## Compatibility

Slack OAuth v2, Sign in with Slack, workspace app distribution.

## Variant phrasings

### slack oauth user cancelled install

Same handling. Cancellation and denial arrive identically; both deserve the explanatory page.

### slack access_denied oauth callback

Check the state value matches what was sent; a mismatched state on a denial still indicates a flow bug worth fixing.

## Why it happens

OAuth is consent-based by design: the user can always say no, and Slack reports that as access_denied. Treating it as an exception to retry misses the point - the user made a choice. The productive response is better explanation and a narrower ask, not more aggressive retrying.

## Edge cases

- Do not confuse with scope errors: invalid scopes fail before the consent screen, denials happen on it
- Workspace app-approval settings can force denials for every user; check with the workspace admin
- Test with a fresh user; admins who already approved see a different screen

## If it still fails

- Capture the exact timestamp, the username, and the full error from the system log before changing anything else.
- Reproduce with a single test user so you are not debugging a crowd.
- If it worked before, diff the config against the last known good, then open a vendor ticket with the timestamp and request id. Never send secrets or private keys.

## Prevention

- Track credential and certificate expiry with alerts, not memory.
- Run a synthetic check per app daily so breakage pages you, not a user.
- Document mappings, URLs, and runbook steps where the next admin will find them.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_RkP7PsjY7TCaigE9yiPIdg
