Add TwoLeggedOAuth2 (OAuth2 client credentials) authentication - #6
Draft
Adrew-Kirts wants to merge 1 commit into
Draft
Add TwoLeggedOAuth2 (OAuth2 client credentials) authentication#6Adrew-Kirts wants to merge 1 commit into
Adrew-Kirts wants to merge 1 commit into
Conversation
Mautic has supported the client_credentials grant since 4.x, but this library only implements authorization_code and refresh_token, both of which require a user in a browser. Server-to-server consumers cannot use either, so they have been carrying a forked auth class instead. Upstream PR mautic#257 was abandoned and PR mautic#269 is still open, so this stays a local addition for now. Based on the official 4.0.0 tag rather than this fork's master, so the diff is exactly the one class we are missing and nothing else. Deliberately does NOT override getQueryParameters(). The inherited implementation appends nothing to the query string of a POST, which is what we want: the access token travels in the Authorization header only. Sending it in both the header and the query string makes Mautic's OAuth2::getBearerToken() find more than one token and reject the request, which surfaces as an opaque HTTP 500 and only on calls that upload a file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
Do not delete this branch or the
4.0.0-patchtag. Portail consumes this code straight from this repository —composer.jsonpinsmautic/api-library: "4.0.0-patch", which resolves to commit0a2d5886onwebmecanik-4.x-two-legged-oauth2. Deleting the tag breaks everycomposer installin Portail immediately, including CI and image builds.This PR is intended to be reviewed and then closed, not merged — merging would offer to delete the head branch, and the branch is where this code lives.
Why
Mautic has supported the
client_credentialsgrant (2-legged OAuth2) since 4.x, but this library only implementsauthorization_codeandrefresh_token— both of which require a user in a browser granting consent. Anything talking to Mautic server-to-server therefore cannot authenticate with the upstream library at all.Upstream has never shipped it: PR mautic#257 (2021) was abandoned, PR mautic#269 (2022) is still open, and issue mautic#332 (Nov 2024) confirms it is still absent from 3.1.0 and 4.0.0-beta. So it stays a local addition.
Two internal consumers need it — Portail (
MauticService) andautomation-magento(Model/ApiClient.php) — both throughApiAuth::newAuth($settings, 'TwoLeggedOAuth2').What
One file:
lib/Auth/TwoLeggedOAuth2.php(185 lines), extending the officialAbstractAuth.Base is upstream
main, not this fork'smaster.masteris a Mautic-5 sync from January 2026 that also carriesEmails::sendCustomToContactand aContacts::create()query-argument override, neither of which any consumer here uses. Basing on upstream directly keeps the diff to exactly the one class we are missing.I first cut this from the
4.0.0tag, but that tag ships a broken functional test suite — 12 failures, every one of them fixed upstream after the tag was cut (AssetsTest×4 → "Using an existing remote asset";CategoriesTest→ "Replacing with an existing remote file also for the category tests";CampaignsTest×2 → "Removing unnecessary campaign creation (duplicate)";StagesTest×6 → "Stage weight must be unique"). The4.0.0..mainrange touches six files, all undertests/, and zero files underlib/— so moving the base tomainchanges no runtime code whatsoever and simply gets a green suite.The one thing to look at in review
This class deliberately does not override
getQueryParameters().master's version does, appending?access_token=…whenever afileparameter is present. Combined with theAuthorization: Bearerheader thatprepareRequest()already sets, the token goes out twice. Mautic'sOAuth2::getBearerToken()collects tokens from headers, form-encoded body and query string, and throws when it finds more than one; the exception escapesOAuthListenerand renders as a generic error-#500 payload.Reproduced against a Mautic 5.4.1 instance,
GET /api/users?limit=1:Authorizationheader only?access_token=only{"errors":[{"message":"Looks like I encountered an error (error #500)…"}]}Because the failure needs a
fileparameter, it only ever showed up on multipart uploads — every other endpoint kept working, which made it look like an instance problem rather than a library one.The inherited
getQueryParameters()returns[]for a POST, so the header is the only token source. UpstreamAbstractAuthalready builds multipart bodies itself (MultipartStream), so nothing further is required.requestAccessToken()also clears any held token before requesting a new one, so a stale bearer is never sent to the token endpoint.Validation
Run against a live Mautic 5.4.1 instance with real API credentials, with this branch in place of
master:getActiveUsers(16 users) →getAtmtAvailablesRoles(3 roles, admin role resolved) →checkApiConnection, all OKopenConnexionserved from the in-process pool, 0.0000sPOST contacts/importCsv(multipart): 200, Mautic created the import entity — the exact call that returns 500 onmasterNotes for reviewers
setup()'s parameter names are load-bearing:ApiAuth::newAuth()resolves arguments by reflecting on them and passesnullfor anything it cannot match. Renaming one silently breaks callers instead of failing loudly.masteris untouched, soautomation-magentois unaffected. It does still carry the double-token behaviour on any file upload it performs.