OAuth2

security

OAuth2

Definition

OAuth 2.0 is an authorization framework defined in RFC 6749 that enables applications to obtain limited access to user accounts on third-party services without exposing user credentials. Rather than sharing passwords between applications, OAuth2 defines a series of authorization flows -- called grant types -- that issue scoped access tokens representing specific permissions. The requesting application receives a token that grants only the access it needs, for a limited time, without ever seeing the user's actual credentials.

OAuth2 draws a sharp distinction between authentication (proving who you are) and authorization (proving what you are allowed to do). While OAuth2 is strictly an authorization protocol, it is frequently extended with OpenID Connect (OIDC) to provide authentication capabilities as well. This layered design allows systems to compose authentication and authorization independently, choosing the right mechanism for each concern.

The protocol has become the de facto standard for API authorization across the industry, used by virtually every major platform including Google, GitHub, Microsoft, and Facebook. Its widespread adoption means that integrating third-party services almost always involves implementing an OAuth2 flow, making it essential knowledge for any platform that consumes or exposes APIs.

Grant Types

OAuth2 defines several grant types, each designed for a specific use case and threat model. The choice of grant type depends on the client type (confidential vs. public), the deployment context (server-side vs. mobile vs. SPA), and the trust relationship between parties.

Grant TypeRFCClient TypeUse CaseSecurity Level
Authorization CodeRFC 6749ConfidentialServer-side web applicationsHigh
Authorization Code + PKCERFC 7636PublicMobile apps, SPAs, CLIsHigh
Client CredentialsRFC 6749ConfidentialService-to-service (M2M)High
Device CodeRFC 8628PublicInput-constrained devices (smart TV, CLI)Medium
Refresh TokenRFC 6749BothToken renewal without re-authenticationHigh
ImplicitRFC 6749PublicBrowser-based apps DEPRECATEDLow
Resource Owner PasswordRFC 6749ConfidentialLegacy migration DEPRECATEDLow

The implicit and resource owner password grants are formally deprecated in the OAuth 2.1 draft (draft-ietf-oauth-v2-1) due to inherent security weaknesses. Modern applications should use Authorization Code with PKCE for all public client scenarios.

Authorization Code Flow

The authorization code flow is the most common and most secure OAuth2 grant type for user-facing applications. It involves a three-party exchange between the user's browser, the authorization server, and the client application:

+--------+                               +---------------+
|        |--(1) Authorization Request --->|               |
|        |                                | Authorization |
| User   |<-(2) Authorization Grant ------|    Server     |
| Agent  |                                |               |
|        |                                +---------------+
+--------+                                       |
    |                                             |
    v                                             |
+--------+                                        |
|        |--(3) Authorization Code + Verifier ---->|
| Client |                                        |
|        |<-(4) Access Token + Refresh Token ------|
|        |                                        |
|        |--(5) API Request + Access Token ------->+---------------+
|        |                                        |   Resource    |
|        |<-(6) Protected Resource ---------------|    Server     |
+--------+                                        +---------------+

The critical security property is that the access token is never exposed to the user agent (browser). The authorization code is exchanged server-side, where the client secret (or PKCE verifier) proves the client's identity.

PKCE Extension

Proof Key for Code Exchange (PKCE, pronounced "pixy") was introduced in RFC 7636 to protect the authorization code flow against interception attacks, particularly on mobile platforms where custom URL schemes can be intercepted by malicious applications.

# PKCE implementation in Elixir
defmodule PrismaticAuth.PKCE do
  @doc "Generate a cryptographically random code verifier"
  @spec generate_verifier() :: String.t()
  def generate_verifier do
    :crypto.strong_rand_bytes(32)
    |> Base.url_encode64(padding: false)
  end

  @doc "Derive the code challenge from the verifier using S256"
  @spec generate_challenge(String.t()) :: String.t()
  def generate_challenge(verifier) do
    :crypto.hash(:sha256, verifier)
    |> Base.url_encode64(padding: false)
  end

  @doc "Verify a code verifier against a stored challenge"
  @spec verify(String.t(), String.t()) :: boolean()
  def verify(verifier, stored_challenge) do
    generate_challenge(verifier) == stored_challenge
  end
end

PKCE works by having the client generate a random code_verifier, derive a code_challenge from it (SHA-256 hash), send the challenge with the authorization request, and later prove possession of the verifier when exchanging the code for a token. Even if the authorization code is intercepted, the attacker cannot exchange it without the original verifier.

Token Lifecycle

OAuth2 tokens follow a defined lifecycle from issuance through expiration or revocation:

PhaseDescriptionTypical Duration
IssuanceToken generated after successful grant exchangeInstantaneous
ActiveToken valid for API requests within scope15 min - 1 hour
RefreshNew access token obtained using refresh tokenSeconds
ExpirationToken becomes invalid after exp timestampAutomatic
RevocationToken explicitly invalidated before expiration (RFC 7009)Immediate

Access tokens are intentionally short-lived (typically 15-60 minutes) to limit the damage window if a token is compromised. Refresh tokens have longer lifetimes (hours to days) but are stored securely server-side and can be individually revoked.

# Token lifecycle management
defmodule PrismaticAuth.TokenManager do
  @access_token_ttl 3600       # 1 hour
  @refresh_token_ttl 604_800   # 7 days

  @spec issue_tokens(User.t(), list(String.t())) ::
    {:ok, %{access_token: String.t(), refresh_token: String.t()}}
  def issue_tokens(user, scopes) do
    access_token = Guardian.encode_and_sign(user, %{
      scopes: scopes,
      roles: user.roles,
      typ: "access"
    }, ttl: {@access_token_ttl, :second})

    refresh_token = Guardian.encode_and_sign(user, %{
      typ: "refresh"
    }, ttl: {@refresh_token_ttl, :second})

    {:ok, %{access_token: access_token, refresh_token: refresh_token}}
  end

  @spec refresh(String.t()) :: {:ok, String.t()} | {:error, :invalid_token}
  def refresh(refresh_token) do
    case Guardian.decode_and_verify(refresh_token, %{"typ" => "refresh"}) do
      {:ok, claims} -> issue_access_token(claims["sub"])
      {:error, _reason} -> {:error, :invalid_token}
    end
  end
end

Scopes and Permission Mapping

OAuth2 scopes define the boundaries of access that a token grants. Scopes are strings that represent specific capabilities, and they map to the platform's RBAC permission model:

OAuth2 ScopeRBAC PermissionDescription
assets:readViewer roleRead asset inventory
assets:writeOperator roleCreate and modify assets
scans:executeOperator roleExecute OSINT/EASM scans
reports:readAnalyst roleAccess security reports
compliance:assessAnalyst roleRun compliance assessments
admin:manageAdmin roleSystem administration

Scope validation occurs at two points: when the token is issued (ensuring the requested scopes are within the user's authorized set) and when the token is used (ensuring the requested operation falls within the token's granted scopes). This double-check prevents both scope escalation and scope drift.

Context in Prismatic

The Prismatic Platform supports OAuth2 for third-party integrations and API access delegation. External services accessing Prismatic API endpoints authenticate through the client credentials flow, receiving JWT access tokens with scoped permissions. The RBAC system maps OAuth2 scopes to platform roles, ensuring that delegated access respects the same permission boundaries as direct authentication.

For user-facing authentication, the platform integrates with the Ueberauth library, which provides a composable, strategy-based approach to OAuth2 authentication with external providers. Each provider (GitHub, Google, GitLab) is configured as a separate Ueberauth strategy, and successful authentication results in a platform JWT being issued with the appropriate role claims.

The API Gateway validates all incoming tokens through Plug middleware, checking token signature, expiration, and scope before routing to backend services. All token exchange occurs over TLS to prevent interception.

# Ueberauth configuration for OAuth2 providers
# config/config.exs
config :ueberauth, Ueberauth,
  providers: [
    github: {Ueberauth.Strategy.Github, [default_scope: "user:email,read:org"]},
    google: {Ueberauth.Strategy.Google, [default_scope: "email profile"]},
    gitlab: {Ueberauth.Strategy.Gitlab, [default_scope: "read_user openid"]}
  ]

# OAuth2 callback controller
defmodule PrismaticWeb.AuthController do
  use PrismaticWeb, :controller

  def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
    user_params = %{
      email: auth.info.email,
      provider: to_string(auth.provider),
      provider_uid: auth.uid
    }

    case Accounts.find_or_create_user(user_params) do
      {:ok, user} ->
        {:ok, token, _claims} = Guardian.encode_and_sign(user)
        json(conn, %{access_token: token})

      {:error, reason} ->
        conn |> put_status(:unauthorized) |> json(%{error: reason})
    end
  end
end

Security Considerations

OAuth2 implementations must address several security concerns to prevent token theft, replay attacks, and privilege escalation:

ThreatMitigationPrismatic Implementation
Authorization code interceptionPKCE (RFC 7636)Mandatory for all public clients
Token theft via XSSHttpOnly, Secure, SameSite cookiesTokens stored server-side, not in localStorage
Redirect URI manipulationExact URI matching (no wildcards)Strict redirect URI validation
Token replayShort TTL + refresh tokens1-hour access tokens, 7-day refresh
Scope escalationServer-side scope validationDouble-check at issuance and usage
CSRF on authorization endpointstate parameter with HMACMandatory for all flows
Refresh token theftToken rotation (RFC 6819)New refresh token on each use

All OAuth2 token exchange MUST occur over TLS. The Prismatic Platform enforces HTTPS for all OAuth2 endpoints and rejects plain HTTP requests at the API Gateway level.

OAuth 2.1 and Future Direction

The OAuth 2.1 draft specification (draft-ietf-oauth-v2-1) consolidates OAuth 2.0, its extensions, and current best practices into a single document. Key changes include:

  • PKCE required for all authorization code grants (not just public clients)
  • Implicit grant removed entirely
  • Resource Owner Password grant removed entirely
  • Redirect URI exact matching mandatory (no pattern matching)
  • Refresh token rotation recommended for all clients
  • Bearer token usage consolidated from RFC 6750

These changes align with what the Prismatic Platform already enforces, as the platform was designed with OAuth 2.1 best practices from the start.

  • JWT - Token format commonly used for OAuth2 access tokens
  • RBAC - Permission model mapping OAuth2 scopes to platform roles
  • REST API - API interface secured with OAuth2 tokens
  • TLS - Transport encryption protecting OAuth2 token exchange
  • API Gateway - Centralized token validation and routing
  • Plug - Elixir middleware implementing OAuth2 validation
  • Rate Limiting - Per-client throttling using OAuth2 client identity
  • Encryption at Rest - Protection of stored refresh tokens and client secrets
  • Observability - Monitoring OAuth2 flows and token usage
  • OpenAPI - API specification documenting OAuth2 security schemes

See Also

  • Architecture - Authentication architecture
  • Apps - Prismatic API application with OAuth2 support
  • Agents - Agent authentication and authorization

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL