Extract GraphiQL client-credentials token provider#7381
Draft
gonzaloriestra wants to merge 1 commit intographiql-store/auth-agnostic-serverfrom
Draft
Extract GraphiQL client-credentials token provider#7381gonzaloriestra wants to merge 1 commit intographiql-store/auth-agnostic-serverfrom
gonzaloriestra wants to merge 1 commit intographiql-store/auth-agnostic-serverfrom
Conversation
4 tasks
Contributor
Author
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
This was referenced Apr 23, 2026
Move the in-memory OAuth client_credentials token strategy out of processes/graphiql.ts into its own graphiql-token-provider.ts so it has a clear seam and is testable in isolation. Adds unit tests for createClientCredentialsTokenProvider covering caching semantics, forced refresh, and the OAuth request shape. Made-with: Cursor
de745ff to
6a55501
Compare
685bb7c to
5297f10
Compare
Contributor
Differences in type declarationsWe detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:
New type declarationspackages/cli-kit/dist/public/node/graphql.d.ts/**
* Returns true if the GraphQL document contains a mutation operation that
* would actually be executed for the given (optional) operation name.
*
* - When `operationName` is provided, only the matching operation is checked.
* - When `operationName` is omitted and the document has a single operation,
* that operation is checked.
* - When the document has multiple operations and no operation name is given,
* any mutation in the document is treated as a mutation request (the GraphQL
* server would reject the ambiguous request anyway).
*
* Returns false for queries, subscriptions, fragment-only documents, and any
* input that fails to parse as GraphQL.
*
* @param query - The GraphQL document to inspect.
* @param operationName - Optional name of the operation to check; when set, only that operation is considered.
* @returns True if the relevant operation is a mutation; false otherwise.
*/
export declare function containsMutation(query: string, operationName?: string): boolean;
packages/cli-kit/dist/public/node/graphiql/server.d.tsimport { Server } from 'http';
import { Writable } from 'stream';
/**
* Derives a deterministic GraphiQL authentication key from the app's API secret and store FQDN.
* The key is stable across dev server restarts (so browser tabs survive restarts)
* but is not guessable without the app secret.
*
* @param apiSecret - The Partners app's client secret used as the HMAC key.
* @param storeFqdn - The myshopify.com domain the GraphiQL session targets.
* @returns A 64-character hex string suitable for use as the `?key=` query param.
*/
export declare function deriveGraphiQLKey(apiSecret: string, storeFqdn: string): string;
/**
* Resolves the GraphiQL authentication key. Uses the explicitly provided key
* if non-empty, otherwise derives one deterministically from the app secret.
*
* @param providedKey - An explicit key supplied by the caller; takes precedence when non-empty.
* @param apiSecret - The Partners app's client secret, used to derive a stable key as a fallback.
* @param storeFqdn - The myshopify.com domain the GraphiQL session targets.
* @returns The resolved key.
*/
export declare function resolveGraphiQLKey(providedKey: string | undefined, apiSecret: string, storeFqdn: string): string;
/**
* Pluggable strategy for obtaining and refreshing the Admin API access token
* that the GraphiQL proxy injects into every request.
*
* - `getToken` may return a cached token; the proxy calls it for every request.
* - `refreshToken` (optional) is invoked when the upstream Admin API returns 401.
* When omitted, the proxy falls back to calling `getToken` again on 401.
*
* Implementations must throw `TokenRefreshError` (or any thrown error) when the
* token cannot be obtained; the proxy renders the unauthorized template in that case.
*/
export interface TokenProvider {
getToken: () => Promise<string>;
refreshToken?: () => Promise<string>;
}
/**
* Optional app-specific context, used to render the app pill and scopes note in the
* GraphiQL header and to drive the deterministic key derivation. Pass when the GraphiQL
* server is hosted as part of `shopify app dev`; omit for app-less use cases such as
* `shopify store execute`.
*/
export interface GraphiQLAppContext {
appName: string;
appUrl: string;
apiSecret: string;
}
export interface SetupGraphiQLServerOptions {
stdout: Writable;
port: number;
storeFqdn: string;
tokenProvider: TokenProvider;
/**
* Authentication key required as a `?key=` query string on every request. When omitted:
* - if `appContext` is provided, derived deterministically from `apiSecret` + `storeFqdn`
* so browser tabs survive dev server restarts.
* - otherwise, generated randomly per process.
*/
key?: string;
appContext?: GraphiQLAppContext;
/**
* When true, the proxy rejects mutation operations with HTTP 400 before forwarding
* them to the Admin API. Use this to mirror non-interactive safety guarantees in the
* interactive UI.
*/
protectMutations?: boolean;
}
/**
* Starts a local HTTP server that hosts the GraphiQL UI and proxies requests to the
* Admin API for the configured store. Authentication is delegated to the supplied
* `tokenProvider`, so the same server can serve both `shopify app dev` and stored-session
* use cases.
*
* @param options - Configuration for the server, including the target store, the
* pluggable token provider, and the local port to bind to.
* @returns The underlying Node `http.Server` instance, already listening on `options.port`.
*/
export declare function setupGraphiQLServer(options: SetupGraphiQLServerOptions): Server;
packages/cli-kit/dist/public/node/graphiql/utilities.d.ts/**
* Filters request headers to extract only custom headers that are safe to forward.
* Blocked headers and non-string values are excluded.
*
* @param headers - The raw incoming request headers.
* @returns The subset of headers that are safe to forward to the Admin API.
*/
export declare function filterCustomHeaders(headers: {
[key: string]: string | string[] | undefined;
}): {
[key: string]: string;
};
packages/cli-kit/dist/public/node/graphiql/templates/graphiql.d.tsexport declare const defaultQuery: string;
interface GraphiQLTemplateOptions {
apiVersion: string;
apiVersions: string[];
appName?: string;
appUrl?: string;
key: string;
storeFqdn: string;
protectMutations?: boolean;
}
export declare function graphiqlTemplate({ apiVersion, apiVersions, appName, appUrl, key, storeFqdn, protectMutations, }: GraphiQLTemplateOptions): string;
export {};
packages/cli-kit/dist/public/node/graphiql/templates/unauthorized.d.tsexport declare const unauthorizedTemplate: string;
Existing type declarationsWe found no diffs with existing type declarations |
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.

WHY are these changes introduced?
Fixes #0000
WHAT is this pull request doing?
How to test your changes?
Post-release steps
Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add