Skip to content

feat: Add OIDC nonce parameter support for CSRF protection #21213

@blinkagent

Description

@blinkagent

Summary

Some OIDC providers require either PKCE (code_challenge) or nonce-based CSRF protection. Coder currently implements neither, which prevents integration with these providers.

This issue proposes implementing OIDC nonce support as an alternative to PKCE for CSRF protection, following RFC draft-ietf-oauth-security-topics Section 4.5.3.2.

Problem

When configuring OIDC authentication with certain identity providers that require PKCE, users receive:

{"message":"Encountered error in oidc process: invalid_request","detail":"Invalid request: Missing CodeChallenge (PKCE - RFC 7636)"}

Coder's OIDC client implementation uses the standard authorization code flow but does not include:

  • PKCE parameters (code_challenge, code_challenge_method, code_verifier)
  • OIDC nonce parameter and validation

Proposed Solution

Implement OIDC nonce support as a CSRF mitigation mechanism. Per the OAuth Security BCP:

With additional precautions, described in Section 4.5.3.2, confidential OpenID Connect clients MAY use the nonce parameter and the respective Claim in the ID Token instead [of PKCE].

Implementation Requirements

Per RFC requirements, the implementation must:

  1. Generate a cryptographically random nonce before the authorization request
  2. Bind nonce to user agent session (store in cookie similar to state parameter)
  3. Include nonce in authorization request using oidc.Nonce() from go-oidc library
  4. Validate nonce in ID Token from token endpoint response
  5. Disregard all tokens until nonce validation succeeds

Technical Approach

The github.com/coreos/go-oidc/v3 library already supports nonce:

// 1. Generate nonce before redirect
nonce, _ := cryptorand.String(32)

// 2. Store nonce in cookie (similar to state)
http.SetCookie(rw, &http.Cookie{
    Name:     "coder_oauth2_nonce",
    Value:    nonce,
    Path:     "/",
    HttpOnly: true,
})

// 3. Add nonce to authorization URL
authURL := config.AuthCodeURL(state, oidc.Nonce(nonce))

// 4. On callback, validate nonce in ID token
verifier := provider.Verifier(&oidc.Config{
    ClientID: clientID,
    // This is currently NOT set in Coder
})

// After verification, manually check nonce claim matches stored value
var claims struct {
    Nonce string `json:"nonce"`
}
idToken.Claims(&claims)
if claims.Nonce != storedNonce {
    // Reject - potential authorization code injection attack
}

Files to Modify

  • coderd/httpmw/oauth2.go - Add nonce generation, storage, and inclusion in auth URL
  • coderd/userauth.go - Add nonce validation in userOIDC() callback handler
  • codersdk/deployment.go - Potentially add config option to enable/disable nonce

Alternative: PKCE Support

Full PKCE support (RFC 7636) would be a more comprehensive solution that also protects public clients. However, nonce is:

  • Simpler to implement (no SHA256 hashing)
  • Sufficient for confidential clients like Coder
  • Already supported by the go-oidc library

A separate issue could track PKCE support if needed.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions