From 5c08737c2bbe9eba115e2ed6241a9f09f9b16854 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sun, 31 Aug 2025 04:42:00 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Document=20Mutual=20TLS=20(mTLS)?= =?UTF-8?q?=20usage=20with=20example=20in=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - closes https://github.com/ruby-oauth/oauth2/issues/583 --- CHANGELOG.md | 1 + README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b78172a..264910a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - improved documentation by @pboling +- Document Mutual TLS (mTLS) usage with example in README (connection_opts.ssl client_cert/client_key and auth_scheme: :tls_client_auth) - documentation notes in code comments and README highlighting OAuth 2.1 differences, with references, such as: - PKCE required for auth code, - exact redirect URI match, diff --git a/README.md b/README.md index a029a2cb..5ea07326 100644 --- a/README.md +++ b/README.md @@ -913,7 +913,55 @@ access.revoke(token_type_hint: :refresh_token) ### Client Configuration Tips -- Authentication schemes for the token request: +#### Mutual TLS (mTLS) client authentication + +Some providers require OAuth requests (including the token request and subsequent API calls) to be sender‑constrained using mutual TLS (mTLS). With this gem, you enable mTLS by providing a client certificate/private key to Faraday via connection_opts.ssl and, if your provider requires it for client authentication, selecting the tls_client_auth auth_scheme. + +Example using PEM files (certificate and key): + +```ruby +require "oauth2" +require "openssl" + +client = OAuth2::Client.new( + ENV.fetch("CLIENT_ID"), + ENV.fetch("CLIENT_SECRET"), + site: "https://example.com", + authorize_url: "/oauth/authorize/", + token_url: "/oauth/token/", + auth_scheme: :tls_client_auth, # if your AS requires mTLS-based client authentication + connection_opts: { + ssl: { + client_cert: OpenSSL::X509::Certificate.new(File.read("localhost.pem")), + client_key: OpenSSL::PKey::RSA.new(File.read("localhost-key.pem")), + # Optional extras, uncomment as needed: + # ca_file: "/path/to/ca-bundle.pem", # custom CA(s) + # verify: true # enable server cert verification (recommended) + }, + }, +) + +# Example token request (any grant type can be used). The mTLS handshake +# will occur automatically on HTTPS calls using the configured cert/key. +access = client.client_credentials.get_token + +# Subsequent resource requests will also use mTLS on HTTPS endpoints of `site`: +resp = access.get("/v1/protected") +``` + +Notes: +- Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"]). +- If your certificate and key are in a PKCS#12/PFX bundle, you can load them like: + - p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"]) + - client_cert = p12.certificate; client_key = p12.key +- Server trust: + - If your environment does not have system CAs, specify ca_file or ca_path inside the ssl: hash. + - Keep verify: true in production. Set verify: false only for local testing. +- Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. net_http (default) and net_http_persistent are common choices. +- Scope of mTLS: The SSL client cert is applied to any HTTPS request made by this client (token and resource requests) to the configured site base URL (and absolute URLs you call with the same client). +- OIDC tie-in: Some OPs require tls_client_auth at the token endpoint per OIDC/OAuth specifications. That is enabled via auth_scheme: :tls_client_auth as shown above. + +#### Authentication schemes for the token request ```ruby OAuth2::Client.new( @@ -924,7 +972,7 @@ OAuth2::Client.new( ) ``` -- Faraday connection, timeouts, proxy, custom adapter/middleware: +#### Faraday connection, timeouts, proxy, custom adapter/middleware: ```ruby client = OAuth2::Client.new( @@ -943,7 +991,10 @@ client = OAuth2::Client.new( end ``` -- Redirection: The library follows up to `max_redirects` (default 5). You can override per-client via `options[:max_redirects]`. +#### Redirection + +The library follows up to `max_redirects` (default 5). +You can override per-client via `options[:max_redirects]`. ### Handling Responses and Errors