Repository navigation
Conversation
`ShopifyAPI::Auth::ClientCredentials.client_credentials` takes an optional `client_assertion:`. When passed, the token request authenticates with the JWT assertion, sends no client secret, and disables inherited HTTP Basic or digest auth for that request only. `ShopifyAPI::Auth::ClientAssertion.create` is an optional stateless signer for `RS256` and `ES256` keys. Client-credentials requests no longer follow redirects for either authentication mode, since the request body carries the app's credentials. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
This branch has not been deployed
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.
Description
Adds JWT client assertion authentication (RFC 7523) to the client credentials grant, so an app can authenticate a token request with a key it owns instead of the client secret.
ShopifyAPI::Auth::ClientCredentials.client_credentialstakes an optionalclient_assertion:. When passed, the request body carries onlyclient_id,grant_type,client_assertion_typeandclient_assertion, with no client secret. Omitting it, or passingnil, keeps today's client-secret behavior.ShopifyAPI::Auth::ClientAssertion.create, an optional helper that signs one assertion with anRS256(RSA, 2048-bit minimum) orES256(P-256) key the app has already loaded. It setskidandtyp: JWT, setsissandsubto the client ID, setsaudto the shop's token endpoint, and adds a randomjti. The default lifetime is 60 seconds; an integer from 1 to 300 is accepted.ShopifyAPI::Errors::InvalidClientAssertionErrorfor a blank or non-Stringassertion and for invalid signer input.AuthorizationorX-Shopify-Access-Tokenheader, URL userinfo, or HTTParty default query params. Turn off inherited Basic/digest auth for that request only, with the newHttpClient#request(..., http_authentication: false).3xxfrom the token endpoint now raises instead of being followed. Other requests and global HTTParty defaults are unchanged.Why
307or308resends the POST body, so following one can forward the client secret or the assertion to whatever host the response names. The token exchange doesn't need redirects.ClientAssertion.createis stateless and doesn't readShopifyAPI::Context. It never imports, generates, stores or rotates keys. Apps that sign elsewhere, for example in a KMS, pass the JWT directly.HttpClient#requestinstead of a separate transport keeps the existing proxy, CA and timeout settings.How has this been tested?
bundle exec rake test: 401 runs, 1,068 assertions, 0 failures.bundle exec srb tc: no errors.bundle exec rubocop: no offenses.ClientAssertion.create(ES256), thenclient_credentials(client_assertion:), then an Admin GraphQLshopquery200ES256assertion, then an Admin GraphQLshopquery200401 invalid_client, no token307from the token endpoint, client secret and assertionclient_credentialsreturns aSession, so the token response's status isn't visible in these runs. The local runs used an app-side signer becauseClientAssertion.createonly builds.myshopify.comaudiences.Checklist:
Generated with Pi.