Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support msw v2 and custom server implementations #223

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
923 changes: 225 additions & 698 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/bl-npm-4.1.0-7f94cdcf3f-9e8521fa7e.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/gopd-npm-1.0.1-10c1d0b534-a5ccfb8806.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/msw-npm-1.2.2-a36db7de78-e42cec8f55.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/ora-npm-5.4.1-4f0343adb7-28d476ee6c.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/rxjs-npm-7.8.1-41c443a75b-de4b53db10.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/tmp-npm-0.0.33-bcbf65df2a-902d7aceb7.zip
Binary file not shown.
Binary file removed .yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,31 @@ const tearDown = async ({ jwksMock, server }) => {

You can also find [this example in the repo](example/authentication.test.js).

### Custom Mock Server Worker (MSW) usage

Internally this library uses Mock Server Worker (MSW) to create network mocks
for the JWKS keyset. This can cause issues if you're already using MSW because
running multiple MSW server instances is not recommended.

In this case, instead of calling `start()/stop()`, use the `jwksHandler()`
function to create an MSW handler you can add to your existing server
instance. Like this:

```typescript
// Import your existing global msw server
import { server } from "./msw-server.js";

describe("example reusing global msw server", () => {
beforeEach(() => {
// Prepend the jwks handler to the server
server.use(jwksMock.jwksHandler())
})
test("does something requiring jwks", () => {
// Test logic
})
})
```

## Under the hood

`createJWKSMock` will create a local PKI and generate a working JWKS.json. Calling `jwksMock.start()` will use [msw](https://mswjs.io/)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
"dependencies": {
"base64-url": "^2.3.3",
"jsonwebtoken": "^9.0.0",
"msw": "^1.2.2",
"msw": "^2.3.1",
"node-forge": "^1.3.1",
"node-rsa": "^1.1.1"
},
Expand Down
23 changes: 16 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { JwtPayload } from 'jsonwebtoken'
import { createJWKS, createKeyPair, signJwt } from './tools.js'
import { setupServer } from 'msw/node'
import { rest } from 'msw'
import { HttpResponse, http, RequestHandlerOptions, HttpHandler } from 'msw'

const createJWKSMock = (
jwksBase: string,
jwksPath = '/.well-known/jwks.json'
) => {
): JWKSMock => {
const keypair = createKeyPair()
const JWKS = createJWKS({
...keypair,
jwksOrigin: jwksBase,
})
const server = setupServer(
rest.get(new URL(jwksPath, jwksBase).href, (_, res, ctx) =>
res(ctx.status(200), ctx.json(JWKS))
const jwksHandler = (options?: RequestHandlerOptions) =>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why allow for providing options here? The only thing one could do it with it is to make a "one time handler" which I find kinda pointless. If the support for injecting options would be dropped, this would not need to be a function any more but could be a property.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I started with it as a property but was trying to make it flexible - e.g. might want to ensure only one request is ever made. I don't currently use that option though so might be overkill. I also saw kid was a function so I kept a similar interface.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, you are right. Another unnecessary function. Possibly I was concerned about people writing to these variables. But I guess that is also a bit paranoid. I will provide the values for now but keep the kid as it is to not break the interface.

http.get(
new URL(jwksPath, jwksBase).href,
() => HttpResponse.json(JWKS),
options
)
)
const server = setupServer(jwksHandler())

const kid = () => JWKS.keys[0].kid

Expand All @@ -35,9 +37,16 @@ const createJWKSMock = (
stop,
kid,
token,
jwksHandler,
}
}

export type JWKSMock = ReturnType<typeof createJWKSMock>
export type JWKSMock = {
start: () => void
stop: () => void
kid: () => string
token: (token: JwtPayload) => string
jwksHandler: (options?: RequestHandlerOptions) => HttpHandler
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jwksHandler: (options?: RequestHandlerOptions) => HttpHandler
createMSWRequestHandler: (options?: RequestHandlerOptions) => HttpHandler

I think it should be somehow clear from the name that this is for msw. Will implement respective changes.

}

export default createJWKSMock
45 changes: 35 additions & 10 deletions src/jwksRsa.test.ts
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the wrong file. We should demonstrate the usage with msw in https://github.com/mlev/mock-jwks/blob/main/example/authentication.test.js which serves as part of the documentation. I will apply these changes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right sorry - that was the only test I saw that tested the mocking functionality. I didn't see the one outside of src.

Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import JWT from 'jsonwebtoken'
import jwksClient, { CertSigningKey, RsaSigningKey } from 'jwks-rsa'
import createAuth0Mock from './index.js'
import createAuth0Mock, { type JWKSMock } from './index.js'
import pify from 'pify'
import { beforeEach, describe, expect, test } from 'vitest'
import { setupServer } from 'msw/node'

const auth0Mock = createAuth0Mock('https://hardfork.eu.auth0.com')
const client = jwksClient({
jwksUri: 'https://hardfork.eu.auth0.com/.well-known/jwks.json',
})

describe('Tests for JWKS being correctly consumed by jwks-rsa client', () => {
beforeEach(() => {
auth0Mock.start()
return auth0Mock.stop
const initialise = () => {
const auth0Mock = createAuth0Mock('https://hardfork.eu.auth0.com')
const client = jwksClient({
jwksUri: 'https://hardfork.eu.auth0.com/.well-known/jwks.json',
})
return { auth0Mock, client }
}

const rsaClientTests = (auth0Mock: JWKSMock, client: jwksClient.JwksClient) => {
test('mock returns a signing key', () =>
expect(pify(client.getSigningKey)(auth0Mock.kid())).resolves.toBeTruthy())
test('generated token should be valid against the JWKS key', async () => {
Expand Down Expand Up @@ -86,4 +86,29 @@ describe('Tests for JWKS being correctly consumed by jwks-rsa client', () => {
'delete:all',
],
}))
}

describe('Tests for JWKS being correctly consumed by jwks-rsa client', () => {
describe('Using built-in msw server mocks', () => {
const { auth0Mock, client } = initialise()

beforeEach(() => {
auth0Mock.start()
return auth0Mock.stop
})

rsaClientTests(auth0Mock, client)
})

describe('Using custom msw server mocks', () => {
const { auth0Mock, client } = initialise()

beforeEach(() => {
const server = setupServer(auth0Mock.jwksHandler())
server.listen()
return () => server.close()
})

rsaClientTests(auth0Mock, client)
})
})
Loading
Loading