Commit Graph
12 Commits
Author SHA1 Message Date
Bobby HoandClaude Opus 5 990d24dc42 feat: add oauth2 scope columns and single-use delete queries (#28007)
OAuth2 tokens issued by Coder ignore scope entirely. The authorize
endpoint parses the `scope` parameter and then discards it, and both
grant paths mint API keys with full API access regardless of what the
client requested or what the app's allowlist permits. There is also
nowhere to put a negotiated scope: nothing carries one from the
authorize step to the token it produces.

Schema and query groundwork for that pipeline. No behavior change on its
own.

- Migration `000569` adds a `scope` column to
`oauth2_provider_app_codes` and `oauth2_provider_app_tokens`, so a
negotiated scope can travel from a code to the token it is exchanged
for, and from a token to its refreshed successor.
- Existing rows are backfilled to `coder:all`, then both columns become
NOT NULL with a non-empty CHECK. Every OAuth2 key is unrestricted in
fact today, so the backfill only writes that down, and a caller that
omits the column now fails instead of silently issuing full access.
- Adds `DeleteOAuth2ProviderAppCodeByIDReturningRow` and
`DeleteAPIKeyByIDReturningRow`, which return `sql.ErrNoRows` when the
row is already gone. Postgres serializes concurrent deletes on the row
lock, so exactly one caller gets a row back, which is what will let the
grant paths enforce single use of a code or refresh token without a
read-then-write race.
- No callers yet. The existing blind deletes and all of their call sites
are untouched, and codes and tokens record `coder:all` until a later
phase negotiates a real value.

Phase 1 of [PLAT-470](https://linear.app/codercom/issue/PLAT-470),
tracked as
[PLAT-478](https://linear.app/codercom/issue/PLAT-478/phase-1-schema-and-queries).
Scope validation at authorize, applying the negotiated scope in the code
grant, and refresh narrowing follow as separate PRs.

Verified locally: `make gen` and `make lint` clean, the migrations suite
passes both up and down, and dbauthz's `TestMethodTestSuite` passes.

<details>
<summary>End-to-end scope enforcement flow (green marks what this PR
touches)</summary>

```mermaid
flowchart TD
    subgraph authorize["/oauth2/authorize"]
        AZ1["ShowAuthorizePage (GET)<br/>renders consent page"]
        AZ2["ProcessAuthorize (POST)<br/>scope parsed, then discarded"]
        Q1["InsertOAuth2ProviderAppCode<br/>gains a Scope param"]
        AZ1 --> AZ2 --> Q1
    end

    Q1 --> CODES[("oauth2_provider_app_codes<br/>new column: scope text NOT NULL")]

    subgraph codegrant["POST /oauth2/token, grant_type=authorization_code"]
        G1["authorizationCodeGrant"]
        Q2["GetOAuth2ProviderAppCodeByPrefix<br/>now returns Scope"]
        Q4["DeleteOAuth2ProviderAppCodeByIDReturningRow<br/>added, no caller yet"]
        G2["apikey.Generate + UserRBACSubject<br/>hardcoded to full access"]
        G1 --> Q2 --> G2
        G1 -.-> Q4
    end

    CODES --> G1
    G2 --> Q3

    Q3["InsertOAuth2ProviderAppToken<br/>gains a Scope param"]
    Q3 --> TOKENS[("oauth2_provider_app_tokens<br/>new column: scope text NOT NULL")]

    subgraph refresh["POST /oauth2/token, grant_type=refresh_token"]
        G3["refreshTokenGrant"]
        Q5["GetOAuth2ProviderAppTokenByPrefix<br/>now returns Scope"]
        Q6["DeleteAPIKeyByIDReturningRow<br/>added, no caller yet"]
        G3 --> Q5
        G3 -.-> Q6
    end

    TOKENS --> G3
    Q5 --> Q3

    subgraph enforce["Every authenticated API request"]
        E1["httpmw ExtractAPIKey"] --> E2["APIKey.ScopeSet()"] --> E3["UserRBACSubject"] --> E4["dbauthz authorize"]
    end

    TOKENS --> E1

    classDef changed fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#1b3c1e
    classDef dormant fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,stroke-dasharray:5 3,color:#1b3c1e
    class Q1,Q2,Q3,Q5,CODES,TOKENS changed
    class Q4,Q6 dormant
```

Solid green is added or changed here. Dashed green exists but has no
caller yet. Everything else is unchanged, including the enforcement
engine at the bottom, which already reads a key's scopes correctly and
only needs real data fed into it.

</details>

<details>
<summary>Suggested reading order</summary>

Most of the diff is generated. `dump.sql`, `models.go`, `querier.go`,
`queries.sql.go`, `check_constraint.go`, and the dbmock and dbmetrics
packages all come from `make gen`.

1. `migrations/000569_oauth2_scope_columns.{up,down}.sql`: additive
column, backfill, NOT NULL, CHECK, and a `COMMENT ON COLUMN` on each.
2. `queries/oauth2.sql` and `queries/apikeys.sql`: `scope` added to both
insert column lists, plus the two new returning-row deletes alongside
the untouched originals. The `Get...ByPrefix` selects needed no edit,
since they are `SELECT *`.
3. `dbauthz/dbauthz.go`: hand-written wrappers for the two new queries,
each fetching by ID, authorizing delete against the fetched object, then
delegating. The generic `deleteQ` helper does not fit, since it requires
the delete to return only `error`.
4. `oauth2provider/authorize.go` and `oauth2provider/tokens.go`: the
only production changes, all behavior-neutral.
5. `dbgen/dbgen.go` and `dbauthz/dbauthz_test.go`: seed threading, plus
a case per new query. `MethodTestSuite` fails with "Method never called"
for anything untested.

Neither type needs to become auditable, which `make lint` confirms by
not erroring on `enterprise/audit/table.go`.

</details>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 09:58:51 -07:00
Bobby Ho 209d1ca498 fix: reject PKCE code_verifier below RFC 7636 length floor (#28003)
The token endpoint accepted any non-empty `code_verifier`, so a
one-character verifier was enough to authenticate. RFC 7636 §4.1
requires 43 to 128 characters from the unreserved set.

That fix plus the related gaps review surfaced in the same path:

- Enforce the length and charset floor on the verifier before the S256
comparison runs.
- Validate the challenge at the authorize endpoint too. It was only
checked for non-emptiness, so a malformed challenge was stored and then
failed late at token exchange, blaming the wrong parameter.
- A malformed verifier now returns `invalid_request` (RFC 6749 §5.2); a
well-formed but wrong one still returns `invalid_grant` (RFC 7636 §4.6).
Both looked identical before, so a client had no way to tell a syntax
error from a hash mismatch and would retry the same bad verifier
forever.
- Revoke the authorization code when a PKCE check fails. Without that, a
leaked code could be replayed with unlimited verifier guesses for its
remaining lifetime, and RFC 6749 §10.5 requires codes to be single use.
- Fix verifier generation in `scripts/oauth2/*.sh` and the docs example.
They deleted reserved base64 characters instead of translating them to
the URL-safe alphabet, so most runs produced verifiers under the new
floor.

Also carries #28041, which merged into this branch: public clients may
register bare custom schemes such as `vscode://` again, with `mailto`,
`tel`, and `sms` rejected.

Split out of #27873 (public OAuth2 client support). PKCE is already
mandatory for every client, so this stands on its own.

<details>
<summary>Manual verification</summary>

Ran against a local dev server on this branch, using a session token and
a throwaway app from `scripts/oauth2/setup-test-app.sh`.

1. Happy path unchanged: HTTP 200, verifier length 43.
2. `code_verifier=short`, and a 43-character verifier ending in `!`:
both HTTP 400 `invalid_request`, so charset is enforced and not just
length.
3. `code_challenge=tooshort` at authorize: HTTP 400 `invalid_request`,
no code issued. An empty challenge still hits the older "required and
cannot be empty" message.
4. Well-formed but wrong verifier: HTTP 400 `invalid_grant`, distinct
from the cases above.
5. Retrying that same code with the correct verifier: HTTP 400, code
already revoked by the failed check.
6. `generate-pkce.sh` produces a 43-character verifier (20 out of 20
runs); the docs example produces 128.
7. `scripts/oauth2/test-mcp-oauth2.sh` passes end to end. The two
bearer-token failures in its output are a pre-existing script bug
(`09c50559f3`, July 2025) that reuses a resource-scoped token against
the real API, not a regression here.

</details>
2026-08-12 13:36:52 -07:00
Faur Ioan-Aurel a8e7f329ac fix: redirect OAuth2 authorization page to dashboard (#24499)
Currently when a user clicks either the Cancel or Allow button on the
authorization page the client app URI is executed but the page does not
land to the main dashboard page, leaving the two buttons open for
multiple clicks from the user. Aside from the potential problems it
might cause by activating the callback URI multiple times, the page also
provides poor UX because users usually expect the authorization tab to
return to the dashboard.

The consent page now executes the OAuth2 callback (auth code on Allow,
`access_denied` on Cancel) and hides the two buttons and updates the
existing description with a user instruction to close the window.
Initial implementation relied on a pop-up window executing the callback
while the main window was redirected to the dashboard main page.
- resolves https://github.com/coder/coder/issues/20323

<!--

If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.

-->
2026-04-27 23:26:17 +03:00
Faur Ioan-Aurel 83fd4cf5c2 fix: OAuth2 cancel button in the authorization page not working (#24058)
Go's html/template has a built-in security filter (urlFilter) that only
allows http, https, and mailto URL schemes. Any other scheme gets
replaced with #ZgotmplZ.

The OAuth2 app's callback URL uses custom URI scheme which the filter
considers unsafe. For example the Coder JetBrains plugin exposes a
callback URI with the scheme jetbrains:// - which was effectively
changed by the template engine into #ZgotmplZ. Of course this is not an
actual callback. When users clicked the cancel button nothing happened.

The fix was simple - we now wrap the apps registered callback URI into
htmltemplate.URL. Usually this needs some validation otherwise the
linter will complain about it. The callback URI used by the Cancel logic
is actually validated by our backend when the client app
programmatically registered via the dynamic OAuth2 registration
endpoints, so we refactored the validation around that code and re-used
some of it in the Cancel handling to make sure we don't allow URIs like
`javascript` and `data`, even though in theory these URIs were already
validated.

In addition, while testing this PR with
https://github.com/coder/coder-jetbrains-toolbox/pull/209 I discovered
that we are also not compliant with
https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 which requires
the server to attach the local state if it was provided by the client in
the original request. Also it is optional but generally a good practice
to include `error_description` in the error responses. In fact we follow
this pattern for the other types of error responses. So this is not a
one off.

- resolves #20323
<img width="1485" height="771" alt="Cancel_page_with_invalid_uri"
src="https://github.com/user-attachments/assets/5539d234-9ce3-4dda-b421-d023fc9aa99e"
/>
<img width="486" height="746" alt="Coder Toolbox handling the Cancel
button"
src="https://github.com/user-attachments/assets/acab71a6-d29c-4fa9-80ba-3c0095bbdc8f"
/>

<!--

If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.

-->
2026-04-10 12:49:22 +03:00
Thomas Kosiewski b776a14b46 fix(coderd): harden OAuth2 provider security (#22194)
## Summary

Harden the OAuth2 provider with multiple security fixes addressing
`coder/security#121` (CSRF session takeover) and converge on OAuth 2.1
compliance.

### Security Fixes

| Fix | Description | Commits |
|-----|-------------|---------|
| **CSRF on `/oauth2/authorize`** | Enforce CSRF protection on the
authorize endpoint POST (consent form submission) | `ba7d646`, `b94a64e`
|
| **Clickjacking: `frame-ancestors` CSP** | Prevent consent page from
being iframed (`Content-Security-Policy: frame-ancestors 'none'` +
`X-Frame-Options: DENY`) | `597aeb2` |
| **Exact redirect URI matching** | Changed from prefix matching to full
string exact matching per OAuth 2.1 §4.1.2.1 | `73d64b1`, `93897f1` |
| **Store & verify `redirect_uri`** | Store redirect_uri with auth code
in DB, verify at token exchange matches exactly (RFC 6749 §4.1.3) |
`50569b9`, `d7ca315` |
| **Mandatory PKCE** | Require `code_challenge` at authorization (for
`response_type=code`) + unconditional `code_verifier` verification at
token exchange | `d7ca315`, `1cda1a9` |
| **Reject implicit grant** | `response_type=token` now returns
`unsupported_response_type` error page (OAuth 2.1 removes implicit flow)
| `d7ca315`, `91b8863` |

### Changes by File

**`coderd/httpmw/csrf.go`** — Extended the CSRF `ExemptFunc` to enforce
CSRF on `/oauth2/authorize` in addition to `/api` routes. The consent
form POST is now CSRF-protected to prevent cross-site authorization code
theft.

**`site/site.go`** — Added `Content-Security-Policy: frame-ancestors
'none'` and `X-Frame-Options: DENY` headers to `RenderOAuthAllowPage`
(consent page only — does not affect the SPA/global CSP used by AI
tasks).

**`coderd/httpapi/queryparams.go`** — Changed `RedirectURL` from prefix
matching (`strings.HasPrefix(v.Path, base.Path)`) to full URI exact
matching (`v.String() != base.String()`), comparing scheme, host, path,
and query.

**`coderd/oauth2provider/authorize.go`** — Added PKCE enforcement:
`code_challenge` is required when `response_type=code` (via a
conditional check, not `RequiredNotEmpty`, so `response_type=token` can
reach the explicit rejection path). `ShowAuthorizePage` (GET) validates
`response_type` before rendering and returns a 400 error page for
unsupported types. `ProcessAuthorize` (POST) stores the `redirect_uri`
with the auth code when explicitly provided.

**`coderd/oauth2provider/tokens.go`** — PKCE verification is now
unconditional (not gated on `code_challenge` being present in DB). If
the stored code has a `redirect_uri`, the token endpoint verifies it
matches exactly — mismatch returns `errBadCode` → `invalid_grant`.
Missing `code_verifier` returns `invalid_grant`.

**`codersdk/oauth2.go`** — `OAuth2ProviderResponseTypeToken` constant
and `Valid()` acceptance are **kept** so the authorize handler can parse
`response_type=token` and return the proper `unsupported_response_type`
error rather than failing at parameter validation.

**`coderd/database/migrations/000421_*`** — Added `redirect_uri text`
column to `oauth2_provider_app_codes`.

### Design Decisions

**`state` parameter remains optional** — The plan initially required
`state` via `RequiredNotEmpty`, but this was reverted in `376a753` to
avoid breaking existing clients. The `state` is still hashed and stored
when provided (via `state_hash` column), securing clients that opt in.

**`response_type=token` kept in `Valid()`** — Removing it from `Valid()`
would cause the parameter parser to reject the request before the
authorize handler can return the proper `unsupported_response_type`
error. The constant is kept for correct error handling flow.

**CSP scoped to consent page only** — `frame-ancestors 'none'` is set
only on the OAuth consent page renderer, not globally. The SPA/global
CSP was previously changed to allow framing for AI tasks
([#18102](https://github.com/coder/coder/pull/18102)); this change does
not regress that.

### Out of Scope (follow-up PRs)

- Bearer tokens in query strings (needs internal caller audit)
- Scope enforcement on OAuth2 tokens
- Rate limiting on dynamic client registration


---

<details>
<summary>📋 Implementation Plan</summary>

# Plan: Harden OAuth2 Provider — Security Fixes + OAuth 2.1 Compliance

## Context & Why

Security issue `coder/security#121` reports a critical session takeover
via CSRF on the OAuth2 provider. This plan covers all remaining security
fixes from that issue **plus** convergence on OAuth 2.1 requirements.
The goal is a single PR that closes all actionable gaps.

## Current State (already committed on branch `csrf-sjx1`)

| Fix | Status | Commits |
|-----|--------|---------|
| Fix 1: CSRF on `/oauth2/authorize` | ✅ Done | `ba7d646`, `b94a64e` |
| CSRF token in consent form HTML | ✅ Done | `b94a64e` |
| `state_hash` column + storage | ✅ Done (hash stored, but state still
optional) | `9167d83`, `b94a64e` |
| Tests for CSRF + state hash | ✅ Done | `e4119b5` |

## Remaining Work

### ~~Fix 2 — Require `state` parameter~~ (DROPPED)

> **Decision:** Do not enforce `state` as required. The `state`
parameter is still hashed and stored when provided (via
`hashOAuth2State` / `state_hash` column from prior commits), but clients
are not forced to supply it. This avoids breaking existing integrations
that omit state.

**Rollback:** Remove `"state"` from the `RequiredNotEmpty` call in
`coderd/oauth2provider/authorize.go:42`:

```go
// BEFORE (current on branch)
p.RequiredNotEmpty("response_type", "client_id", "state", "code_challenge")

// AFTER
p.RequiredNotEmpty("response_type", "client_id", "code_challenge")
```

No test changes needed — tests already pass `state` voluntarily.

### Fix 4 — Exact redirect URI matching

Currently `coderd/httpapi/queryparams.go:233` uses prefix matching:

```go
// CURRENT — prefix match
if v.Host != base.Host || !strings.HasPrefix(v.Path, base.Path) {
```

OAuth 2.1 requires **exact string matching**. Change to:

```go
// AFTER — exact match (OAuth 2.1 §4.1.2.1)
if v.Host != base.Host || v.Path != base.Path {
```

**File: `coderd/httpapi/queryparams.go` — `RedirectURL` method**

Also update the error message from "must be a subset of" to "must
exactly match".

**Additionally**, store `redirect_uri` with the auth code and verify at
the token endpoint (RFC 6749 §4.1.3):

1. **New migration** (same migration file or a new `000421`): Add
`redirect_uri text` column to `oauth2_provider_app_codes`
2. **Update INSERT query** in `coderd/database/queries/oauth2.sql` to
include `redirect_uri`
3. **`coderd/oauth2provider/authorize.go`**: Store
`params.redirectURL.String()` when inserting the code
4. **`coderd/oauth2provider/tokens.go`**: After retrieving the code from
DB, verify that `redirect_uri` from the token request matches the stored
value exactly. Currently `tokens.go:103` calls `p.RedirectURL(vals,
callbackURL, "redirect_uri")` for prefix validation only — it must
compare against the stored redirect_uri from the code, not just the
app's callback URL.

<details>
<summary>Why both exact match AND store+verify?</summary>

Exact matching at the authorize endpoint prevents open redirectors
(attacker can't use a sub-path).
Storing and verifying at the token endpoint prevents code injection — an
attacker who steals a code can't exchange it with a different
redirect_uri than was originally authorized. This is required by RFC
6749 §4.1.3 and OAuth 2.1.
</details>

### Fix 7 — `frame-ancestors` CSP on consent page

The consent page can be iframed by a workspace app (same-site), which is
the attack vector. Add a `Content-Security-Policy` header to prevent
framing.

**File: `site/site.go` — `RenderOAuthAllowPage` function (~line 731)**

Before writing the response, add:

```go
func RenderOAuthAllowPage(rw http.ResponseWriter, r *http.Request, data RenderOAuthAllowData) {
    rw.Header().Set("Content-Type", "text/html; charset=utf-8")
    // Prevent the consent page from being framed to mitigate
    // clickjacking attacks (coder/security#121).
    rw.Header().Set("Content-Security-Policy", "frame-ancestors 'none'")
    rw.Header().Set("X-Frame-Options", "DENY")
    ...
```

Both headers for defense-in-depth (CSP for modern browsers,
X-Frame-Options for legacy).

### OAuth 2.1 — Mandatory PKCE

Currently PKCE is checked only when `code_challenge` was provided during
authorization (`tokens.go:258`):

```go
// CURRENT — conditional check
if dbCode.CodeChallenge.Valid && dbCode.CodeChallenge.String != "" {
    // verify PKCE
}
```

OAuth 2.1 requires PKCE for ALL authorization code flows. Change to:

**File: `coderd/oauth2provider/authorize.go`** — Add `"code_challenge"`
to required params:

```go
p.RequiredNotEmpty("response_type", "client_id", "code_challenge")
```

**File: `coderd/oauth2provider/tokens.go:257-265`** — Make PKCE
verification unconditional:

```go
// AFTER — PKCE always required (OAuth 2.1)
if req.CodeVerifier == "" {
    return codersdk.OAuth2TokenResponse{}, errInvalidPKCE
}
if !dbCode.CodeChallenge.Valid || dbCode.CodeChallenge.String == "" {
    // Code was issued without a challenge — should not happen
    // with the authorize endpoint enforcement, but defend in
    // depth.
    return codersdk.OAuth2TokenResponse{}, errInvalidPKCE
}
if !VerifyPKCE(dbCode.CodeChallenge.String, req.CodeVerifier) {
    return codersdk.OAuth2TokenResponse{}, errInvalidPKCE
}
```

**File: `codersdk/oauth2.go`** — Remove
`OAuth2ProviderResponseTypeToken` from the enum or reject it explicitly
in the authorize handler. Currently it's defined at line 216 but the
handler ignores `response_type` and always issues a code. We should
either:
- (a) Remove the `"token"` variant from the enum and reject it with
`unsupported_response_type`, OR
- (b) Add an explicit check in `ProcessAuthorize` that rejects
`response_type=token`

Option (b) is simpler and more backwards-compatible:

```go
// In ProcessAuthorize, after extracting params:
if params.responseType != codersdk.OAuth2ProviderResponseTypeCode {
    httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest,
        codersdk.OAuth2ErrorCodeUnsupportedResponseType,
        "Only response_type=code is supported")
    return
}
```

### OAuth 2.1 — Bearer tokens in query strings

`coderd/httpmw/apikey.go:743` accepts `access_token` from URL query
parameters. OAuth 2.1 prohibits this. However, this may be used
internally (e.g., workspace apps, DERP). Need to audit callers before
removing.

**Approach:** This is a larger change with potential breakage. Mark as a
**separate follow-up issue** rather than including in this PR. Document
the finding.

### OAuth 2.1 — Removed flows

✅ **Already compliant.** `tokens.go` only supports `authorization_code`
and `refresh_token` grant types. The implicit grant
(`response_type=token`) will be explicitly rejected per the PKCE section
above.

### OAuth 2.1 — Refresh token rotation

✅ **Already compliant.** `tokens.go:442` deletes the old API key when a
refresh token is used.

## Migration Plan

All DB changes can go in a single new migration (or extend 000420 if the
branch is rebased before merge). Columns to add:
- `redirect_uri text` on `oauth2_provider_app_codes`

The `state_hash` column is already added by migration 000420.

## Implementation Order

1. **Fix 7** — CSP headers on consent page (isolated, no deps)
2. ~~**Fix 2** — Require `state` parameter~~ (DROPPED — state stays
optional)
3. **Fix 4** — Exact redirect URI matching + store/verify redirect_uri
4. **PKCE mandatory** — Require `code_challenge` + reject
`response_type=token`
5. **Rollback** — Remove `"state"` from `RequiredNotEmpty` in
`authorize.go`
6. **Tests** — Update/add tests for all changes
7. **`make gen`** after DB changes

## Out of Scope (separate PRs)

- Bearer tokens in query strings (needs internal caller audit)
- Scope enforcement on OAuth2 tokens
- Rate limiting / quota on dynamic client registration

</details>

---
_Generated with [`mux`](https://github.com/coder/mux) • Model:
`anthropic:claude-opus-4-6` • Thinking: `xhigh`_
2026-02-23 12:18:44 +01:00
Ehab Younes 6683d807ac refactor: add RFC-compliant enum types and use SDK as source of truth (#21468)
Add comprehensive OAuth2 enum types to codersdk following RFC specifications:
- OAuth2ProviderGrantType (RFC 6749)
- OAuth2ProviderResponseType (RFC 6749)
- OAuth2TokenEndpointAuthMethod (RFC 7591)
- OAuth2PKCECodeChallengeMethod (RFC 7636)
- OAuth2TokenType (RFC 6749, RFC 9449)
- OAuth2RevocationTokenTypeHint (RFC 7009)
- OAuth2ErrorCode (RFC 6749, RFC 7009, RFC 8707)

Add OAuth2TokenRequest, OAuth2TokenResponse, OAuth2TokenRevocationRequest,
and OAuth2Error structs to the SDK. Update OAuth2ClientRegistrationRequest,
OAuth2ClientRegistrationResponse, OAuth2ClientConfiguration, and
OAuth2AuthorizationServerMetadata to use typed enums instead of raw strings.

This makes codersdk the single source of truth for OAuth2 types, eliminating
duplication between SDK and server-side structs.

Closes #21476
2026-01-15 12:41:28 +03:00
Asher 3a0e8af6e3 feat: add view workspace button to app error page (#20960)
Closes #19984 

As part of this, I refactored the error template to take in a slice of
actions rather than using individual booleans and strings to control the
behavior.

We decided a link resolves the issue for now so that is what I added,
although we may want to consider a way to start the workspace and follow
the logs dynamically on that page and then show the app when finished
(similar to the tasks page), or at least make the link automatically
start the workspace instead of only taking you to the dashboard where
you have to then start the workspace.
2025-12-08 14:16:00 -09:00
Steven Masley 13ca9ead3a chore!: ensure consistent secret token generation and hashing (#20388)
This PR uses the same sha256 hashing technique as we use for APIKeys. So
now all randomly generated secrets will be hashed with sha256 for
consistency.

This is a breaking change for the oauth tokens. Since oauth is only
allowed for dev builds and experimental, this is ok.
2025-10-23 15:38:49 -05:00
Thomas Kosiewski 251f787743 fix: normalize oauth2 scope parsing (#20359) 2025-10-19 13:20:48 +02:00
Thomas Kosiewski 7b06fc77ae refactor: simplify OAuth2 authorization flow and use 302 redirects (#18923)
# Refactor OAuth2 Provider Authorization Flow

This PR refactors the OAuth2 provider authorization flow by:

1. Removing the `authorizeMW` middleware and directly implementing its functionality in the `ShowAuthorizePage` handler
2. Simplifying function signatures by removing unnecessary parameters:
   - Removed `db` parameter from `ShowAuthorizePage`
   - Removed `accessURL` parameter from `ProcessAuthorize`
3. Changing the redirect status code in `ProcessAuthorize` from 307 (Temporary Redirect) to 302 (Found) to improve compatibility with external OAuth2 apps and browsers. (Technical explanation: we replied with a 307 to a POST request, thus the browser performs a redirect to that URL as a POST request, but we need it to be a GET request to be compatible. Thus, we use the 302 redirect so that browsers turn it into a GET request when redirecting back to the redirect_uri.)

The changes maintain the same functionality while simplifying the code and improving compatibility with external systems.
2025-07-20 16:22:52 +02:00
Thomas Kosiewski 071383bbe8 feat: add RFC 9728 OAuth2 resource metadata support (#18920)
# Enhanced OAuth2 and MCP Compliance for API Authentication

This PR improves OAuth2 and MCP (Microsoft Cloud for Sovereignty)
compliance by:

1. Adding RFC 9728 compliant `WWW-Authenticate` headers with resource
metadata URLs
2. Passing the configured `AccessURL` to API key middleware for proper
audience validation
3. Creating specialized CORS handling for OAuth2 and MCP endpoints with
appropriate headers
4. Making the `state` parameter optional in OAuth2 authorization
requests

These changes ensure proper OAuth2 token audience validation against the
configured access URL and improve interoperability with OAuth2 clients
by providing better error responses and metadata discovery.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
2025-07-19 22:05:15 +02:00
Thomas Kosiewski c65013384a refactor: move OAuth2 provider code to dedicated package (#18746)
# Refactor OAuth2 Provider Code into Dedicated Package

This PR refactors the OAuth2 provider functionality by moving it from the main `coderd` package into a dedicated `oauth2provider` package. The change improves code organization and maintainability without changing functionality.

Key changes:

- Created a new `oauth2provider` package to house all OAuth2 provider-related code
- Moved existing OAuth2 provider functionality from `coderd/identityprovider` to the new package
- Refactored handler functions to follow a consistent pattern of returning `http.HandlerFunc` instead of being handlers directly
- Split large files into smaller, more focused files organized by functionality:
  - `app_secrets.go` - Manages OAuth2 application secrets
  - `apps.go` - Handles OAuth2 application CRUD operations
  - `authorize.go` - Implements the authorization flow
  - `metadata.go` - Provides OAuth2 metadata endpoints
  - `registration.go` - Handles dynamic client registration
  - `revoke.go` - Implements token revocation
  - `secrets.go` - Manages secret generation and validation
  - `tokens.go` - Handles token issuance and validation

This refactoring improves code organization and makes the OAuth2 provider functionality more maintainable while preserving all existing behavior.
2025-07-03 20:24:45 +02:00