feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(nextjs): Introduce clerkMiddleware - #2404

Merged
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware
Jan 3, 2024
Merged

feat(nextjs): Introduce clerkMiddleware#2404
nikosdouvlis merged 7 commits into
mainfrom
nikos/introduce-clerkmiddleware

Conversation

@nikosdouvlis

@nikosdouvlisnikosdouvlis commented Dec 19, 2023

Copy link
Copy Markdown
Member

Description

clerkMiddleware API:

export default clerkMiddleware(auth => {
auth().protect();
auth().protect({ permission: 'org:domains:delete' });
auth().protect({ permission: 'org:domains:delete' }, { redirectUrl: 'https://clerk.com' });
auth().redirectToSignIn();
auth().redirectToSignIn({ returnBackUrl: 'https://clerk.com' });
const { userId, redirectToSignIn } = auth();
if (!userId) {
return redirectToSignIn();
}
return NextResponse.next();
});

protect API:

The protect API can be used to protect pages, route handlers, server actions and any other request that can be intercepted using the middleware. It can be used to handle both unauthenticated and unauthorized requests.

Calling protect without arguments will catch all unauthenticated requests, and depending on the context it's used in, it will either redirect to the sign-in URL (for page requests) or throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

Calling protect with the same arguments the has API accepts, will catch all unauthorized requests, and throw a notFound error (for route handlers and server actions). If a redirectUrl is provided, protect will always respect it and redirect to it.

SDK-647

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:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

@changeset-bot

changeset-botBot commented Dec 19, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 832ca35

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

This PR includes changesets to release 2 packages
NameType
@clerk/nextjsPatch
@clerk/elementsPatch

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

Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/nextjs/src/app-router/server/auth.ts
Comment threadpackages/nextjs/src/pages/__tests__/index.test.tsx
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/types/src/multiDomain.ts
Comment threadpackages/nextjs/src/server/types.ts
const res = new Response(null, { status: 307, headers: requestState.headers });
return decorateResponseWithObservabilityHeaders(res, requestState);
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error('Clerk: handshake status without redirect');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🙃 let's use the errorThrower instead of Error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nikosdouvlis Reminder ☝️

Comment threadpackages/backend/src/tokens/clerkRequest.ts
Comment threadpackages/nextjs/src/server/authMiddleware.ts
Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
switch (e.message) {
case PROTECT_REWRITE:
// Rewrite to bonus URL to force not found error
return NextResponse.rewrite(`${clerkRequest.clerkUrl.origin}/clerk_${Date.now()}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤔 this might pollute a consumer's logs, it's probably fine but we should call this out in the docs.

Comment threadpackages/nextjs/src/server/clerkMiddleware.ts
Comment threadpackages/backend/src/tokens/clerkUrl.ts
Comment threadpackages/types/src/multiDomain.ts Outdated
Comment threadpackages/nextjs/src/server/protect.ts Outdated
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from bb318b2 to 0939fdaCompareDecember 22, 2023 16:44
Comment on lines +110 to +130
const isServerActionRequest = (req: Request) => {
return (
!!req.headers.get(nextConstants.Headers.NextUrl) &&
(req.headers.get(constants.Headers.Accept)?.includes('text/x-component') ||
req.headers.get(constants.Headers.ContentType)?.includes('multipart/form-data') ||
!!req.headers.get(nextConstants.Headers.NextAction))
);
};

const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req))
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
// };

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Many thanks to @panteliselef for taking the time to investigate, test, and help me simplify these checks 🥇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we remove the commented out code?

@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 0939fda to 2ba6cc6CompareDecember 22, 2023 16:51
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 2ba6cc6 to 00c6a30CompareJanuary 3, 2024 01:12
@nikosdouvlis
nikosdouvlisforce-pushed the nikos/introduce-clerkmiddleware branch from 00c6a30 to 8b7c324CompareJanuary 3, 2024 16:26
We're leveraging the isPageRequest and isServerActionRequest checks to detect whether the current request is a page request. For page requests, instead of throwing a 404 or 401, we're redirecting to SIGN_IN_URL automatically.
In order to achieve that, we're using native headers but also the `next-urk` and `next-action` headers of NextJS
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@nikosdouvlis@brkalow@SokratisVidros@dimkl@panteliselef@clerk-cookie