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

chore(express): Refactor requireAuth and clerkMiddleware middlewares #4234

Merged
merged 13 commits into from
Sep 27, 2024

Conversation

wobsoriano
Copy link
Member

@wobsoriano wobsoriano commented Sep 26, 2024

Description

This PR refactors both clerkMiddleware and requireAuth Express middlewares to simplify DX and reduce confusion when choosing between middlewares:

  1. clerkMiddleware

Removed option to pass a handler. It does not do anything at all, which can be replaced by a one-liner from userland:

// Before
app.use(clerkMiddleware((req, res, next) => {
  // req.auth here is undefined so it's not useful at all
}));

// After
app.use(clerkMiddleware());
app.use((req, res, next) => {
  // do stuff
})
  1. requireAuth

Is now using the same logic as clerkMiddleware (use authenticateRequest and attach auth to request), except it will redirect to signInUrl if unauthenticated instead of calling next(new Error()). So you can have requireAuth middleware without clerkMiddleware:

// Before - sends error to error middleware
app.use(clerkMiddleware());
app.get('/protected', requireAuth, (req, res) => {
  res.send('This is a protected route');
});

app.use((err, req, res, next) => {
  if (err instanceof UnauthorizedError) {
    res.status(401).send('Unauthorized');
  } else {
    next(err);
  }
});


// After - redirects to signInUrl

// Apply centralized middleware
app.use(requireAuth());

// Apply middleware to a specific route
app.get('/protected', requireAuth({ signInUrl: '/sign-in }), (req, res) => {
  res.send('This is a protected route');
});

Other removed exports:

  • UnauthorizedError (not used anywhere)
  • ForbiddenError (not used anywhere)

Documentation for this one is still being worked on, we didn't announce the SDK formally, so I think minor update is good. The documentation will have migration guide from @clerk/clerk-sdk-node to @clerk/express. anyway.

Resolves ECO-201

Checklist

  • npm test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Copy link

changeset-bot bot commented Sep 26, 2024

🦋 Changeset detected

Latest commit: a7eb3eb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/express Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@wobsoriano wobsoriano changed the title chore(express): Refactor requireAuth to redirect to sign in page instead of forwarding error chore(express): Refactor requireAuth and clerkMiddleware middlewares Sep 26, 2024
Comment on lines -115 to -125
it('throws error if clerkMiddleware is not executed before requireAuth', async () => {
const customMiddleware: RequestHandler = (_request, response, next) => {
response.setHeader('x-custom-middleware', 'custom');
return next();
};

const response = await runMiddleware([requireAuth, customMiddleware]).expect(500);

assertNoDebugHeaders(response);
expect(response.header).not.toHaveProperty('x-clerk-auth-custom', 'custom-value');
});
Copy link
Member Author

Choose a reason for hiding this comment

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

Removed this tests as requireAuth does not rely on clerkMiddleware anymore

Comment on lines -86 to -113
it('supports usage with request handler: app.use(clerkMiddleware(requestHandler))', async () => {
const handler: RequestHandler = (_req, res, next) => {
res.setHeader('x-clerk-auth-custom', 'custom-value');
return next();
};

const response = await runMiddleware(clerkMiddleware(handler, { enableHandshake: true }), {
Cookie: '__clerk_db_jwt=deadbeef;',
}).expect(200, 'Hello world!');

expect(response.header).toHaveProperty('x-clerk-auth-custom', 'custom-value');
assertSignedOutDebugHeaders(response);
});

it('supports usage with parameters and request handler: app.use(clerkMiddleware(requestHandler, options))', async () => {
const handler: RequestHandler = (_req, res, next) => {
res.setHeader('x-clerk-auth-custom', 'custom-value');
return next();
};
const options = { publishableKey: 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k', enableHandshake: true };

const response = await runMiddleware(clerkMiddleware(handler, options), {
Cookie: '__clerk_db_jwt=deadbeef;',
}).expect(200, 'Hello world!');

expect(response.header).toHaveProperty('x-clerk-auth-custom', 'custom-value');
assertSignedOutDebugHeaders(response);
});
Copy link
Member Author

Choose a reason for hiding this comment

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

Removed as we removed option to pass a handler in clerkMiddleware

@wobsoriano wobsoriano marked this pull request as ready for review September 26, 2024 23:36
Copy link
Contributor

@jescalan jescalan left a comment

Choose a reason for hiding this comment

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

This is beautiful I love it. Such a quick turnaround!

const enableHandshake = options.enableHandshake || false;

// eslint-disable-next-line @typescript-eslint/no-misused-promises
const middleware: RequestHandler = async (request, response, next) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Have we covered for the scenario where someone uses both middlewares? A little conditional at the top of this helper to just call next if auth is already on the request I think would take care of that. May be worth adding a test for this as well.

Copy link
Member Author

@wobsoriano wobsoriano Sep 27, 2024

Choose a reason for hiding this comment

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

Nope, but added just now and a test too!

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to error if there are both? Could we just add a conditional check for req.auth at the top of the function instead?

Copy link
Member Author

@wobsoriano wobsoriano Sep 27, 2024

Choose a reason for hiding this comment

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

Ah I get you now, we want to allow usage of both, but check for req.auth first then call next() if exists so we dont have to run the same logic etc 👍🏼 Perfect for scenario like

app.use(clerkMiddleware());

app.get('/protected', requireAuth(), (req, res) => {
  res.send('This is a protected route');
});

Updated

@LauraBeatris
Copy link
Member

I love those changes, the DX is much better now ❤️

Copy link
Contributor

@jescalan jescalan left a comment

Choose a reason for hiding this comment

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

This looks great to me! Nice work Rob 👏

@wobsoriano wobsoriano merged commit 40bf962 into main Sep 27, 2024
20 checks passed
@wobsoriano wobsoriano deleted the rob/make-express-middleware-handle-auth-logic branch September 27, 2024 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants