Skip to content

Commit

Permalink
Merge pull request #22 from takagimeow/add-get-token-parameter
Browse files Browse the repository at this point in the history
Add getToken() parameter
  • Loading branch information
takagimeow authored Feb 25, 2023
2 parents a8b344b + ec17ef0 commit 80c4198
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 7 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
[![npm version](https://badge.fury.io/js/remix-auth-jwt.svg)](https://badge.fury.io/js/remix-auth-jwt)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)


A Remix Auth strategy for working with JWT.

This strategy is influenced by Ktor's JSON Web Tokens-related library.
This strategy is influenced by Ktor's JSON Web Tokens-related library and the express-jwt library.

In other words, when Remix is used as an API-only application, this strategy comes into effect.

## Supported runtimes

| Runtime | Has Support |
| ---------- | ----------- |
| Node.js ||
| Cloudflare ||
| Node.js | |
| Cloudflare | |

This strategy has been tested to work with Node.js as well as with Cloudflare workers.

Expand Down Expand Up @@ -51,6 +50,16 @@ Check out [this repository](https://github.com/takagimeow/remix-auth-jwt-cloudfl

<!-- If it doesn't support one runtime, explain here why -->

## API

The parameter passed as the first argument when this strategy class is initialized contains the following:

| Name | Type | Description |
| ---------- | ---------------------------------------------------------------------- | --------------------------------------------------- |
| secret | string | The secret used to sign the token. |
| algorithms | Algorithm[] | The algorithms used to sign the token. |
| getToken? | (req: Request) => string \| undefined \| Promise<string \| undefined>; | A function that returns the token from the request. |

## How to use

<!-- Explain how to use the strategy, here you should tell what options it expects from the developer when instantiating the strategy -->
Expand Down
14 changes: 12 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface JwtStrategyOptions {
* The algorithms to verify the JWT
*/
algorithms: Algorithm[];

getToken?: (req: Request) => string | undefined | Promise<string | undefined>;
}

/**
Expand All @@ -45,6 +47,7 @@ export class JwtStrategy<User> extends Strategy<User, JwtStrategyVerifyParams> {
protected secret: string;
protected algorithms: Algorithm[];
protected jwt: JsonwebtokenService;
protected getToken?: JwtStrategyOptions["getToken"];

constructor(
options: JwtStrategyOptions,
Expand All @@ -54,16 +57,23 @@ export class JwtStrategy<User> extends Strategy<User, JwtStrategyVerifyParams> {
this.secret = options.secret;
this.algorithms = options.algorithms;
this.jwt = container.resolve<JsonwebtokenService>("JsonwebtokenService");

if (options.getToken) {
this.getToken = options.getToken;
}
}

async authenticate(
request: Request,
sessionStorage: SessionStorage,
options: AuthenticateOptions
): Promise<User> {
let token: string | undefined;
try {
// Validating Authorisation headers using jsonwebtoken.
const token = request.headers.get("Authorization")?.split(" ")[1];
token = this.getToken
? await this.getToken(request)
: request.headers.get("Authorization")?.split(" ")[1];

if (token == undefined) {
return await this.failure(
"Format is Authorization: Bearer [token]",
Expand Down
58 changes: 57 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "reflect-metadata";
import { createCookieSessionStorage } from "@remix-run/node";
import { createCookieSessionStorage, Request } from "@remix-run/node";
// import * as jwt from "jsonwebtoken-esm";
import { AuthenticateOptions, AuthorizationError } from "remix-auth";
import { container } from "tsyringe";
Expand Down Expand Up @@ -120,6 +120,32 @@ describe(JwtStrategy, () => {
});
});

test("should pass token returned by getToken function to verify function", async () => {
const request = new Request("http://localhost:3000", {
headers: {
Authorization: `Bearer ${token}`,
},
});

const strategy = new JwtStrategy<User>(
{
...options,
getToken: (req) => req.headers.get("Authorization")?.split(" ")[1],
},
verify
);

await strategy.authenticate(request, sessionStorage, {
...BASE_OPTIONS,
});
expect(verify).toBeCalledWith({
payload: {
...payload,
iat: expect.any(Number),
},
});
});

test("should pass error as cause on failure", async () => {
verify.mockImplementationOnce(() => {
throw new TypeError("Invalid bearer token");
Expand Down Expand Up @@ -194,4 +220,34 @@ describe(JwtStrategy, () => {
new Error(JSON.stringify({ message: "Invalid bearer token" }, null, 2))
);
});

test("should raise an error if getToken returns undefined", async () => {
const request = new Request("http://localhost:3000", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const strategy = new JwtStrategy<User>(
{
...options,
getToken: () => {
// eslint-disable-next-line unicorn/no-useless-undefined
return undefined;
},
},
verify
);
const result = await strategy
.authenticate(request, sessionStorage, {
...BASE_OPTIONS,
throwOnError: true,
})
.catch((error) => error);
expect(result).toEqual(
new AuthorizationError("Format is Authorization: Bearer [token]")
);
expect((result as AuthorizationError).cause).toEqual(
new Error("Format is Authorization: Bearer [token]")
);
});
});

0 comments on commit 80c4198

Please sign in to comment.