Skip to content

Commit b8dae42

Browse files
committed
Add support for the JWT token authentication method for the new JWT Auth connection
1 parent 9c7e0bf commit b8dae42

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

packages/api-client-core/spec/GadgetConnection-suite.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,43 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
143143
expect(result.data).toEqual({ meta: { appName: "some app" } });
144144
});
145145

146+
it("should allow connecting with a JWT from an external system", async () => {
147+
nock("https://someapp.gadget.app")
148+
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
149+
.reply(200, function () {
150+
expect(this.req.headers["authorization"]).toEqual([`Bearer foobarbaz`]);
151+
152+
return {
153+
data: {
154+
meta: {
155+
appName: "some app",
156+
},
157+
},
158+
};
159+
});
160+
161+
const connection = new GadgetConnection({
162+
endpoint: "https://someapp.gadget.app/api/graphql",
163+
authenticationMode: { jwt: "foobarbaz" },
164+
});
165+
166+
const result = await connection.currentClient
167+
.query(
168+
gql`
169+
{
170+
meta {
171+
appName
172+
}
173+
}
174+
`,
175+
{}
176+
)
177+
.toPromise();
178+
179+
expect(result.error).toBeUndefined();
180+
expect(result.data).toEqual({ meta: { appName: "some app" } });
181+
});
182+
146183
describe("session token storage", () => {
147184
it("should allow connecting with no session in a session storage mode", async () => {
148185
nock("https://someapp.gadget.app")

packages/api-client-core/src/ClientOptions.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,37 @@ export enum BrowserSessionStorageType {
8181

8282
/** Describes how to authenticate an instance of the client with the Gadget platform */
8383
export interface AuthenticationModeOptions {
84-
// Use an API key to authenticate with Gadget.
85-
// Not strictly required, but without this the client might be useless depending on the app's permissions.
84+
/**
85+
* Use an API key to authenticate with Gadget.
86+
* Not strictly required, but without this the client might be useless depending on the app's permissions.
87+
*/
8688
apiKey?: string;
8789

88-
// Use a web browser's `localStorage` or `sessionStorage` to persist authentication information.
89-
// This allows the browser to have a persistent identity as the user navigates around and logs in and out.
90+
/**
91+
* Use a web browser's `localStorage` or `sessionStorage` to persist authentication information.
92+
* This allows the browser to have a persistent identity as the user navigates around and logs in and out.
93+
*/
9094
browserSession?: boolean | BrowserSessionAuthenticationModeOptions;
9195

92-
// Use no authentication at all, and get access only to the data that the Unauthenticated backend role has access to.
96+
/**
97+
* Use no authentication at all, and get access only to the data that the Unauthenticated backend role has access to.
98+
*/
9399
anonymous?: true;
94100

95-
// @private Use an internal platform auth token for authentication
96-
// This is used to communicate within Gadget itself and shouldn't be used to connect to Gadget from other systems
101+
/**
102+
* Use a JWT token signed by another system to authenticate with Gadget. Requires the JWT Auth Plugin to be set up in your Gadget app.
103+
*/
104+
jwt?: string;
105+
106+
/**
107+
* @private Use an internal platform auth token for authentication
108+
* This is used to communicate within Gadget itself and shouldn't be used to connect to Gadget from other systems
109+
*/
97110
internalAuthToken?: string;
98111

99-
// @private Use a passed custom function for managing authentication. For some fancy integrations that the API client supports, like embedded Shopify apps, we use platform native features to authenticate with the Gadget backend.
112+
/**
113+
* @private Use a passed custom function for managing authentication. For some fancy integrations that the API client supports, like embedded Shopify apps, we use platform native features to authenticate with the Gadget backend.
114+
*/
100115
custom?: {
101116
processFetch(input: RequestInfo | URL, init: RequestInit): Promise<void>;
102117
processTransactionConnectionParams(params: Record<string, any>): Promise<void>;

packages/api-client-core/src/GadgetConnection.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export enum AuthenticationMode {
6161
APIKey = "api-key",
6262
InternalAuthToken = "internal-auth-token",
6363
Anonymous = "anonymous",
64+
ExternalJWT = "jwt",
6465
Custom = "custom",
6566
}
6667

@@ -149,6 +150,8 @@ export class GadgetConnection {
149150
this.authenticationMode = AuthenticationMode.InternalAuthToken;
150151
} else if (options.apiKey) {
151152
this.authenticationMode = AuthenticationMode.APIKey;
153+
} else if (options.jwt) {
154+
this.authenticationMode = AuthenticationMode.ExternalJWT;
152155
} else if (options.custom) {
153156
this.authenticationMode = AuthenticationMode.Custom;
154157
}
@@ -430,6 +433,8 @@ export class GadgetConnection {
430433
connectionParams.auth.token = this.options.authenticationMode!.internalAuthToken!;
431434
} else if (this.authenticationMode == AuthenticationMode.BrowserSession) {
432435
connectionParams.auth.sessionToken = this.sessionTokenStore!.getItem(this.sessionStorageKey);
436+
} else if (this.authenticationMode == AuthenticationMode.ExternalJWT) {
437+
connectionParams.auth.jwt = this.options.authenticationMode!.jwt!;
433438
} else if (this.authenticationMode == AuthenticationMode.Custom) {
434439
await this.options.authenticationMode?.custom?.processTransactionConnectionParams(connectionParams);
435440
}
@@ -464,6 +469,8 @@ export class GadgetConnection {
464469
headers.authorization = "Basic " + base64("gadget-internal" + ":" + this.options.authenticationMode!.internalAuthToken!);
465470
} else if (this.authenticationMode == AuthenticationMode.APIKey) {
466471
headers.authorization = `Bearer ${this.options.authenticationMode?.apiKey}`;
472+
} else if (this.authenticationMode == AuthenticationMode.ExternalJWT) {
473+
headers.authorization = `Bearer ${this.options.authenticationMode?.jwt}`;
467474
} else if (this.authenticationMode == AuthenticationMode.BrowserSession) {
468475
const val = this.sessionTokenStore!.getItem(this.sessionStorageKey);
469476
if (val) {

0 commit comments

Comments
 (0)