Skip to content

Commit

Permalink
Add support for the JWT token authentication method for the new JWT A…
Browse files Browse the repository at this point in the history
…uth connection
  • Loading branch information
airhorns committed Oct 25, 2023
1 parent 9c7e0bf commit a9501c6
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 8 deletions.
37 changes: 37 additions & 0 deletions packages/api-client-core/spec/GadgetConnection-suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,43 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
expect(result.data).toEqual({ meta: { appName: "some app" } });
});

it("should allow connecting with a JWT from an external system", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(200, function () {
expect(this.req.headers["authorization"]).toEqual([`Bearer foobarbaz`]);

return {
data: {
meta: {
appName: "some app",
},
},
};
});

const connection = new GadgetConnection({
endpoint: "https://someapp.gadget.app/api/graphql",
authenticationMode: { jwt: "foobarbaz" },
});

const result = await connection.currentClient
.query(
gql`
{
meta {
appName
}
}
`,
{}
)
.toPromise();

expect(result.error).toBeUndefined();
expect(result.data).toEqual({ meta: { appName: "some app" } });
});

describe("session token storage", () => {
it("should allow connecting with no session in a session storage mode", async () => {
nock("https://someapp.gadget.app")
Expand Down
31 changes: 23 additions & 8 deletions packages/api-client-core/src/ClientOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,37 @@ export enum BrowserSessionStorageType {

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

// Use a web browser's `localStorage` or `sessionStorage` to persist authentication information.
// This allows the browser to have a persistent identity as the user navigates around and logs in and out.
/**
* Use a web browser's `localStorage` or `sessionStorage` to persist authentication information.
* This allows the browser to have a persistent identity as the user navigates around and logs in and out.
*/
browserSession?: boolean | BrowserSessionAuthenticationModeOptions;

// Use no authentication at all, and get access only to the data that the Unauthenticated backend role has access to.
/**
* Use no authentication at all, and get access only to the data that the Unauthenticated backend role has access to.
*/
anonymous?: true;

// @private Use an internal platform auth token for authentication
// This is used to communicate within Gadget itself and shouldn't be used to connect to Gadget from other systems
/**
* Use a JWT token signed by some other system. Requires the JWT Auth Connection to be set up in your Gadget app.
*/
jwt?: string;

/**
* @private Use an internal platform auth token for authentication
* This is used to communicate within Gadget itself and shouldn't be used to connect to Gadget from other systems
*/
internalAuthToken?: string;

// @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.
/**
* @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.
*/
custom?: {
processFetch(input: RequestInfo | URL, init: RequestInit): Promise<void>;
processTransactionConnectionParams(params: Record<string, any>): Promise<void>;
Expand Down
7 changes: 7 additions & 0 deletions packages/api-client-core/src/GadgetConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export enum AuthenticationMode {
APIKey = "api-key",
InternalAuthToken = "internal-auth-token",
Anonymous = "anonymous",
ExternalJWT = "jwt",
Custom = "custom",
}

Expand Down Expand Up @@ -149,6 +150,8 @@ export class GadgetConnection {
this.authenticationMode = AuthenticationMode.InternalAuthToken;
} else if (options.apiKey) {
this.authenticationMode = AuthenticationMode.APIKey;
} else if (options.jwt) {
this.authenticationMode = AuthenticationMode.ExternalJWT;
} else if (options.custom) {
this.authenticationMode = AuthenticationMode.Custom;
}
Expand Down Expand Up @@ -430,6 +433,8 @@ export class GadgetConnection {
connectionParams.auth.token = this.options.authenticationMode!.internalAuthToken!;
} else if (this.authenticationMode == AuthenticationMode.BrowserSession) {
connectionParams.auth.sessionToken = this.sessionTokenStore!.getItem(this.sessionStorageKey);
} else if (this.authenticationMode == AuthenticationMode.ExternalJWT) {
connectionParams.auth.jwt = this.options.authenticationMode!.jwt!;
} else if (this.authenticationMode == AuthenticationMode.Custom) {
await this.options.authenticationMode?.custom?.processTransactionConnectionParams(connectionParams);
}
Expand Down Expand Up @@ -464,6 +469,8 @@ export class GadgetConnection {
headers.authorization = "Basic " + base64("gadget-internal" + ":" + this.options.authenticationMode!.internalAuthToken!);
} else if (this.authenticationMode == AuthenticationMode.APIKey) {
headers.authorization = `Bearer ${this.options.authenticationMode?.apiKey}`;
} else if (this.authenticationMode == AuthenticationMode.ExternalJWT) {
headers.authorization = `Bearer ${this.options.authenticationMode?.jwt}`;
} else if (this.authenticationMode == AuthenticationMode.BrowserSession) {
const val = this.sessionTokenStore!.getItem(this.sessionStorageKey);
if (val) {
Expand Down

0 comments on commit a9501c6

Please sign in to comment.