syntax = "proto3";

package envoy.extensions.filters.http.oauth2.v3;

import "envoy/config/core/v3/base.proto";
import "envoy/config/core/v3/http_uri.proto";
import "envoy/config/route/v3/route_components.proto";
import "envoy/extensions/transport_sockets/tls/v3/secret.proto";
import "envoy/type/matcher/v3/path.proto";

import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.http.oauth2.v3";
option java_outer_classname = "OauthProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/oauth2/v3;oauth2v3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: OAuth]
// OAuth :ref:`configuration overview <config_http_filters_oauth>`.
// [#extension: envoy.filters.http.oauth2]
//

// OAuth cookie configuration attributes.
//
message CookieConfig {
  enum SameSite {
    DISABLED = 0;
    STRICT = 1;
    LAX = 2;
    NONE = 3;
  }

  // The value used for the SameSite cookie attribute.
  SameSite same_site = 1 [(validate.rules).enum = {defined_only: true}];

  // The path attribute for the cookie.
  //
  // This controls the scope of the cookie and is useful for path-based routing scenarios
  // where different logical boundaries or applications may operate with different OAuth2 clients.
  // The CSRF cookie (nonce cookie) can be configured with a different path than session cookies
  // to support flows where the callback URL is on a different path.
  //
  // If not specified, defaults to ``/``.
  string path = 2 [(validate.rules).string = {pattern: "^$|^/[^\\x00-\\x1f\\x7f \",;<>\\\\]*$"}];

  // If true, the ``Partitioned`` attribute will be set on the cookie.
  //
  // Modern browsers (Firefox, Chrome with third-party cookie deprecation) warn or block
  // "foreign" cookies unless they carry the ``Partitioned`` attribute alongside ``SameSite=None; Secure``.
  // When Envoy is used in a gateway/IdP flow that sets OAuth/OIDC cookies for a parent domain
  // (e.g., ``Domain=.example.com``) while running on a different host, those cookies are
  // considered third-party and will be rejected without ``Partitioned``.
  //
  // See `CHIPS <https://developers.google.com/privacy-sandbox/3pcd/chips>`_ for more information.
  //
  // Default is false.
  bool partitioned = 3;
}

// [#next-free-field: 8]
message CookieConfigs {
  // Configuration for the bearer token cookie.
  CookieConfig bearer_token_cookie_config = 1;

  // Configuration for the OAuth HMAC cookie.
  CookieConfig oauth_hmac_cookie_config = 2;

  // Configuration for the OAuth expires cookie.
  CookieConfig oauth_expires_cookie_config = 3;

  // Configuration for the ID token cookie.
  CookieConfig id_token_cookie_config = 4;

  // Configuration for the refresh token cookie.
  CookieConfig refresh_token_cookie_config = 5;

  // Configuration for the OAuth nonce cookie.
  CookieConfig oauth_nonce_cookie_config = 6;

  // Configuration for the code verifier cookie.
  CookieConfig code_verifier_cookie_config = 7;
}

// [#next-free-field: 6]
message OAuth2Credentials {
  // [#next-free-field: 8]
  message CookieNames {
    // Cookie name to hold OAuth bearer token value. When the authentication server validates the
    // client and returns an authorization token back to the OAuth filter, no matter what format
    // that token is, if :ref:`forward_bearer_token <envoy_v3_api_field_extensions.filters.http.oauth2.v3.OAuth2Config.forward_bearer_token>`
    // is set to true the filter will send over the bearer token as a cookie with this name to the
    // upstream. Defaults to ``BearerToken``.
    string bearer_token = 1
        [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}];

    // Cookie name to hold OAuth HMAC value. Defaults to ``OauthHMAC``.
    string oauth_hmac = 2
        [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}];

    // Cookie name to hold OAuth expiry value. Defaults to ``OauthExpires``.
    string oauth_expires = 3
        [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}];

    // Cookie name to hold the id token. Defaults to ``IdToken``.
    string id_token = 4
        [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}];

    // Cookie name to hold the refresh token. Defaults to ``RefreshToken``.
    string refresh_token = 5
        [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}];

    // Cookie name to hold the nonce value. Defaults to ``OauthNonce``.
    string oauth_nonce = 6
        [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}];

    // Cookie name to hold the PKCE code verifier. Defaults to ``OauthCodeVerifier``.
    string code_verifier = 7
        [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME ignore_empty: true}];
  }

  // The client_id to be used in the authorize calls. This value will be URL encoded when sent to the OAuth server.
  string client_id = 1 [(validate.rules).string = {min_len: 1}];

  // The secret used to retrieve the access token. This value will be URL encoded when sent to the OAuth server.
  // This field is required unless :ref:`auth_type <envoy_v3_api_field_extensions.filters.http.oauth2.v3.OAuth2Config.auth_type>`
  // is set to ``TLS_CLIENT_AUTH``, in which case authentication is done via the client certificate.
  // When ``auth_type`` is ``PRIVATE_KEY_JWT``, this field must contain the PEM-encoded private key
  // used to sign the JWT client assertion.
  transport_sockets.tls.v3.SdsSecretConfig token_secret = 2;

  // Configures how the secret token should be created.
  oneof token_formation {
    option (validate.required) = true;

    // If present, the secret token will be a HMAC using the provided secret.
    transport_sockets.tls.v3.SdsSecretConfig hmac_secret = 3
        [(validate.rules).message = {required: true}];
  }

  // The cookie names used in OAuth filters flow.
  CookieNames cookie_names = 4;

  // The domain to set the cookie on. If not set, the cookie will default to the host of the request, not including the subdomains.
  // This is useful when token cookies need to be shared across multiple subdomains.
  string cookie_domain = 5
      [(validate.rules).string = {pattern: "^$|^[^\\x00-\\x1f\\x7f \",;<>\\\\]+$"}];
}

// Configuration for ``PRIVATE_KEY_JWT`` client authentication (RFC 7523).
message PrivateKeyJwtConfig {
  // Supported JWT signing algorithms for the client assertion.
  enum SigningAlgorithm {
    // ``RSASSA-PKCS1-v1_5`` using SHA-256.
    RS256 = 0;

    // ``RSASSA-PKCS1-v1_5`` using SHA-384.
    RS384 = 1;

    // ``RSASSA-PKCS1-v1_5`` using SHA-512.
    RS512 = 2;

    // ECDSA using P-256 and SHA-256.
    ES256 = 3;

    // ECDSA using P-384 and SHA-384.
    ES384 = 4;

    // ECDSA using P-521 and SHA-512.
    ES512 = 5;
  }

  // The signing algorithm to use for the JWT assertion.
  // The private key provided in ``token_secret`` must match the algorithm family: an RSA key for
  // the ``RS*`` algorithms, or an EC key for the ``ES*`` algorithms.
  // Default: ``RS256``.
  SigningAlgorithm signing_algorithm = 1 [(validate.rules).enum = {defined_only: true}];

  // The lifetime of the JWT assertion. After this duration, the assertion expires.
  // The value is truncated to whole seconds, so it must be at least ``1s`` when set.
  // Default: ``60s``.
  google.protobuf.Duration assertion_lifetime = 2 [(validate.rules).duration = {gte {seconds: 1}}];
}

// Defines how an OAuth token is forwarded upstream.
message OAuth2TokenForwarding {
  // The upstream request header that will carry the token.
  // Pseudo-headers (names starting with ``:``) and the ``Host`` header are not allowed.
  string header = 1
      [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_NAME strict: false}];
}

// Configuration for the ``post_logout_redirect_uri`` parameter used in OpenID Connect
// `RP-Initiated Logout requests <https://openid.net/specs/openid-connect-rpinitiated-1_0.html>`_.
// This configuration is ignored if ``end_session_endpoint`` is not set.
message PostLogoutRedirectUri {
  oneof config {
    option (validate.required) = true;

    // Do not include the ``post_logout_redirect_uri`` parameter in requests to the
    // configured ``end_session_endpoint``.
    bool disabled = 1 [(validate.rules).bool = {const: true}];

    // URI to send as the ``post_logout_redirect_uri`` parameter. Supports header formatting
    // tokens, and will be percent-encoded automatically when building the logout URL.
    //
    // The URI should be registered with the authorization server.
    string uri = 2 [(validate.rules).string = {min_len: 1}];
  }
}

// OAuth config
//
// [#next-free-field: 34]
message OAuth2Config {
  enum AuthType {
    // The ``client_id`` and ``client_secret`` will be sent in the URL encoded request body.
    // This type should only be used when Auth server does not support Basic authentication.
    URL_ENCODED_BODY = 0;

    // The ``client_id`` and ``client_secret`` will be sent using HTTP Basic authentication scheme.
    BASIC_AUTH = 1;

    // The client will be authenticated using mutual TLS (mTLS) with a client certificate.
    // The ``client_secret`` is not required and will not be sent in the request to the
    // authorization server.
    // The client certificate must be configured in the cluster used by ``token_endpoint`` via
    // transport socket configuration.
    // This implements OAuth 2.0 Mutual-TLS Client Authentication as defined in RFC 8705.
    TLS_CLIENT_AUTH = 2;

    // The client authenticates using a signed JWT assertion (RFC 7523).
    // The ``token_secret`` in credentials must contain the PEM-encoded private key used to sign the assertion.
    // The JWT assertion is sent as ``client_assertion`` in the token request body along with
    // ``client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer``.
    PRIVATE_KEY_JWT = 3;
  }

  // Endpoint on the authorization server to retrieve the access token from.
  config.core.v3.HttpUri token_endpoint = 1;

  // Specifies the retry policy for requests to the OAuth server. If not specified, then no retries will be performed.
  config.core.v3.RetryPolicy retry_policy = 18;

  // The endpoint redirect to for authorization in response to unauthorized requests.
  string authorization_endpoint = 2 [(validate.rules).string = {min_len: 1}];

  // The endpoint at the authorization server to request the user be logged out of the Authorization server.
  // This field is optional and should be set only if openid is in the auth_scopes and the authorization server
  // supports the OpenID Connect RP-Initiated Logout specification.
  // For more information, see https://openid.net/specs/openid-connect-rpinitiated-1_0.html
  //
  // If configured, the OAuth2 filter will redirect users to this endpoint when they access the signout_path.
  string end_session_endpoint = 23;

  // Optional control for the ``post_logout_redirect_uri`` parameter sent to the ``end_session_endpoint`` when a user
  // accesses the ``signout_path``.
  // This field should be set only if ``openid`` is in the ``auth_scopes``, the ``end_session_endpoint`` is configured,
  // and the authorization server supports the OpenID Connect RP-Initiated Logout specification.
  //
  // If unset, Envoy preserves the historical behavior and sends ``<scheme>://<host>/``, constructed from the inbound
  // request, as ``post_logout_redirect_uri``.
  PostLogoutRedirectUri post_logout_redirect_uri = 33;

  // Credentials used for OAuth.
  OAuth2Credentials credentials = 3 [(validate.rules).message = {required: true}];

  // The redirect URI passed to the authorization endpoint. Supports header formatting
  // tokens. For more information, including details on header value syntax, see the
  // documentation on :ref:`custom request headers <config_http_conn_man_headers_custom_request_headers>`.
  //
  // This URI should not contain any query parameters.
  string redirect_uri = 4 [(validate.rules).string = {min_len: 1}];

  // Matching criteria used to determine whether a path appears to be the result of a redirect from the authorization server.
  type.matcher.v3.PathMatcher redirect_path_matcher = 5
      [(validate.rules).message = {required: true}];

  // The path to sign a user out, clearing their credential cookies.
  type.matcher.v3.PathMatcher signout_path = 6 [(validate.rules).message = {required: true}];

  // Forward the OAuth token as a Bearer to upstream web service.
  bool forward_bearer_token = 7;

  // Forward the OIDC ID token to the upstream.
  //
  // If the configured header is ``Authorization``, Envoy forwards the ID token using the
  // ``Bearer`` prefix. For any other header, Envoy forwards the raw token value.
  // If not specified, the ID token will not be forwarded.
  //
  // This can not be configured with :ref:`forward_bearer_token
  // <envoy_v3_api_field_extensions.filters.http.oauth2.v3.OAuth2Config.forward_bearer_token>`
  // or :ref:`preserve_authorization_header
  // <envoy_v3_api_field_extensions.filters.http.oauth2.v3.OAuth2Config.preserve_authorization_header>`
  // when the header is ``Authorization``.
  OAuth2TokenForwarding forward_id_token = 31;

  // If set to true, preserve the existing authorization header.
  // By default the client strips the existing authorization header before forwarding upstream.
  // Can not be set to true if forward_bearer_token is already set to true.
  // Default value is false.
  bool preserve_authorization_header = 16;

  // Any request that matches any of the provided matchers will be passed through without OAuth validation.
  repeated config.route.v3.HeaderMatcher pass_through_matcher = 8;

  // Optional list of OAuth scopes to be claimed in the authorization request. If not specified,
  // defaults to "user" scope.
  // OAuth RFC https://tools.ietf.org/html/rfc6749#section-3.3
  repeated string auth_scopes = 9;

  // Optional resource parameter for authorization request
  // RFC: https://tools.ietf.org/html/rfc8707
  repeated string resources = 10;

  // Defines how ``client_id`` and ``client_secret`` are sent in OAuth client to OAuth server requests.
  // RFC https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
  AuthType auth_type = 11 [(validate.rules).enum = {defined_only: true}];

  // If set to true, allows automatic access token refresh using the associated refresh token (see
  // `RFC 6749 section 6 <https://datatracker.ietf.org/doc/html/rfc6749#section-6>`_), provided that the OAuth server supports that.
  // Default value is true.
  google.protobuf.BoolValue use_refresh_token = 12;

  // The default lifetime in seconds of the access token, if omitted by the authorization server.
  //
  // If this value is not set, it will default to ``0s``. In this case, the expiry must be set by
  // the authorization server or the OAuth flow will fail.
  google.protobuf.Duration default_expires_in = 13;

  // Any request that matches any of the provided matchers won't be redirected to OAuth server when tokens are not valid.
  // Automatic access token refresh will be performed for these requests, if enabled.
  // This behavior can be useful for AJAX requests.
  repeated config.route.v3.HeaderMatcher deny_redirect_matcher = 14;

  // The default lifetime in seconds of the refresh token, if the exp (expiration time) claim is omitted in the refresh token or the refresh token is not JWT.
  //
  // If this value is not set, it will default to ``604800s``. In this case, the cookie with the refresh token will be expired
  // in a week.
  // This setting is only considered if ``use_refresh_token`` is set to true, otherwise the authorization server expiration or ``default_expires_in`` is used.
  google.protobuf.Duration default_refresh_token_expires_in = 15;

  // If set to true, the client will not set a cookie for ID Token even if one is received from the Identity Provider. This may be useful in cases where the ID
  // Token is too large for HTTP cookies (longer than 4096 characters). Enabling this option will only disable setting the cookie response header, the filter
  // will still process incoming ID Tokens as part of the HMAC if they are there. This is to ensure compatibility while switching this setting on. Future
  // sessions would not set the IdToken cookie header.
  bool disable_id_token_set_cookie = 17;

  // If set to true, the client will not set a cookie for Access Token even if one is received from the Identity Provider.
  // Enabling this option will only disable setting the cookie response header, the filter
  // will still process incoming Access Tokens as part of the HMAC if they are there. This is to ensure compatibility while switching this setting on. Future
  // sessions would not set the Access Token cookie header.
  bool disable_access_token_set_cookie = 19;

  // If set to true, the client will not set a cookie for Refresh Token even if one is received from the Identity Provider.
  // Enabling this option will only disable setting the cookie response header, the filter
  // will still process incoming Refresh Tokens as part of the HMAC if they are there. This is to ensure compatibility while switching this setting on. Future
  // sessions would not set the Refresh Token cookie header.
  bool disable_refresh_token_set_cookie = 20;

  // Controls for attributes that can be set on the cookies.
  CookieConfigs cookie_configs = 21;

  // Optional additional prefix to use when emitting statistics.
  string stat_prefix = 22;

  // Optional expiration time for the CSRF protection token cookie.
  // The CSRF token prevents cross-site request forgery attacks during the OAuth2 flow.
  // If not specified, defaults to ``600s`` (10 minutes), which should provide sufficient time
  // for users to complete the OAuth2 authorization flow.
  google.protobuf.Duration csrf_token_expires_in = 24;

  // Optional expiration time for the code verifier cookie.
  // The code verifier is stored in a secure, HTTP-only cookie during the OAuth2 authorization process.
  // If not specified, defaults to ``600s`` (10 minutes), which should provide sufficient time
  // for users to complete the OAuth2 authorization flow.
  google.protobuf.Duration code_verifier_token_expires_in = 25;

  // Disable token encryption. When set to true, both the access token and the ID token will be stored in plain text.
  // This option should only be used in secure environments where token encryption is not required.
  // Default is false (tokens are encrypted).
  bool disable_token_encryption = 26;

  // Any request that matches any of the provided matchers will be allowed to continue to upstream
  // even if OAuth validation fails (missing, invalid, or expired credentials).
  // This is useful for services that can handle both authenticated and unauthenticated requests,
  // enabling graceful degradation patterns.
  //
  // When triggered, all OAuth cookies are stripped from the request and the request proceeds as unauthenticated.
  // Context headers ``x-envoy-oauth-status: failed`` and ``x-envoy-oauth-failure-reason`` are added to inform upstream.
  //
  // Note: If a request matches pass_through_matcher, it bypasses OAuth validation and this matcher won't be evaluated.
  // This matcher takes precedence over deny_redirect_matcher.
  repeated config.route.v3.HeaderMatcher allow_failed_matcher = 27;

  // Optional base URI (scheme + host, e.g. ``https://app.example.com``) used to build the
  // original request URI that is encoded into the OAuth2 ``state`` parameter.
  // This URI will be used later to redirect users on a successful OAuth.
  //
  // This is useful when Envoy sits behind a gateway or load balancer that terminates the
  // user-facing hostname: In that case, the post-authentication redirect derived from ``state`` would
  // send the user to an internal host they didn't request.
  //
  // Supports request header formatting tokens.
  //
  // Example:
  //
  //    original_request_uri: "%REQ(x-forwarded-proto?:scheme)%://%REQ(x-forwarded-host?:authority)%"
  //
  // If not set, defaults to ``<:scheme>://<:authority>`` of the incoming request.
  string original_request_uri = 28;

  // Optional list of domains that are allowed as
  // 1. redirect_uri: which is what the IdP calls after OAuth
  // 2. original_request_uri: the one extracted from the state of an OAuth callback (where should the request go after OAuth)
  //
  // This mitigates:
  // - injecting a malicious x-forwarded-host or any header that is used to template the redirect urls
  // - open redirect attacks where an attacker crafts a ``state`` value pointing to an untrusted host.
  //
  // Each entry is matched against the host (with any port stripped) extracted from the
  // formatted ``redirect_uri``, the formatted ``original_request_uri``, and the URL decoded from
  // the ``state`` parameter on callback. Matching is case-insensitive and supports two forms:
  //
  // * Exact match, e.g. ``example.com`` matches only ``example.com``.
  // * Wildcard subdomain match using a leading ``*.``, e.g. ``*.example.com`` matches
  //   ``foo.example.com`` and ``bar.baz.example.com`` but not ``example.com`` itself.
  //
  // IPv6 literals must be configured without surrounding brackets (e.g. ``::1``, not ``[::1]``).
  //
  // If this list is empty (the default), all hosts are allowed and no validation is performed.
  repeated string allowed_redirect_domains = 29;

  // If set to true, the expiration time for the ID token cookie will always be derived from the
  // ``expires_in`` field of the access token response rather than from the ``exp`` claim in the
  // ID token JWT. This is useful when the access token response advertises a longer lifetime than
  // the ID token and you want the ID token cookie to remain valid for that full duration.
  // Default is false (use the ID token's own ``exp`` claim when available).
  bool use_access_token_expiry_for_id_token_cookie = 30;

  // Configuration for ``PRIVATE_KEY_JWT`` client authentication.
  // Only used when :ref:`auth_type <envoy_v3_api_field_extensions.filters.http.oauth2.v3.OAuth2Config.auth_type>`
  // is set to ``PRIVATE_KEY_JWT``.
  PrivateKeyJwtConfig private_key_jwt_config = 32;
}

// Per-route OAuth2 config.
//
// This message supplies an OAuth2Config for the matched route.
// It overrides the filter-level config for requests matching the route.
// If neither the global config nor a per-route config is specified, OAuth2 is disabled for the route.
message OAuth2PerRoute {
  // Full OAuth2 config for this route.
  OAuth2Config config = 1 [(validate.rules).message = {required: true}];
}

// Filter config.
message OAuth2 {
  // The OAuth2 filter config.
  OAuth2Config config = 1;
}
