stefano
stefano9mo ago

Eliminating redundant auth checks

hi, I'm repeating this piece of code in basically every convex function to check if the user is logged in (first check before any other check for authorizations). is this best practice? (I'm using Clerk)
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return [];
}
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return [];
}
19 Replies
jamwt
jamwt9mo ago
Hi! Eventually, most teams roll this into something like customFunctions from convex-helpers
jamwt
jamwt9mo ago
Customizing serverless functions without middleware
Re-use code and centralize request handler definitions with discoverability and type safety and without the indirection of middleware or nesting of wr...
jamwt
jamwt9mo ago
that allows you to generate a custom query builder that automatically enforces authentication for any given handler
stefano
stefanoOP9mo ago
thanks! will look into this
stefano
stefanoOP9mo ago
related... I don't think I understood how to make sure if the user is logged in. Is the following code enough?
export const testa = mutation({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return null;
}
return "authenticated";
},
});
export const testa = mutation({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return null;
}
return "authenticated";
},
});
Because if I don't check the "act as a user" checkbox in the dashboard it returns null. But if I check it and leave as default it doesn't return null, even though I didn't set a valid "subject"
No description
Nishil Faldu
Nishil Faldu9mo ago
@stefano
I am going to try and answer your question, not sure if thats what you need. But I'd recommend throwing an error when a user is not authenticated and catching it on your frontend. Moreover I have something like the following setup for my projects:
const queryAuthCheck = customCtx(async (ctx: QueryCtx) => {
// Look up the logged in user or get identity
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new ConvexError(
"Oops! It looks like you're not signed in/up. Please log in to continue.",
);
}

if (!identity.emailVerified) {
throw new ConvexError(
"Oops! Your email address hasn't been verified yet. Please check your inbox for the verification email.",
);
}

const user = await getUserByClerkId(ctx, identity.subject);
if (!user) {
throw new ConvexError(
"We couldn't find a user with that Clerk ID. Please check the Clerk ID and try again.",
);
}

return { db: ctx.db, user: user, storage: ctx.storage };
});

export const query = customQuery(queryRaw, queryAuthCheck);
const queryAuthCheck = customCtx(async (ctx: QueryCtx) => {
// Look up the logged in user or get identity
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new ConvexError(
"Oops! It looks like you're not signed in/up. Please log in to continue.",
);
}

if (!identity.emailVerified) {
throw new ConvexError(
"Oops! Your email address hasn't been verified yet. Please check your inbox for the verification email.",
);
}

const user = await getUserByClerkId(ctx, identity.subject);
if (!user) {
throw new ConvexError(
"We couldn't find a user with that Clerk ID. Please check the Clerk ID and try again.",
);
}

return { db: ctx.db, user: user, storage: ctx.storage };
});

export const query = customQuery(queryRaw, queryAuthCheck);
And now instead of using query from convex use the exported query from above
Ica
Ica9mo ago
How do I handle it in Front End? If I use useQuery (/convex/react I keep getting the Convex Error in the UI: Error: [CONVEX Q(example:getData)] [Request ID: ] Server Error Uncaught Error: Authentication required .
"use client"

export default function App(){
const data = useQuery(api.example.getData, {});

return (
...map data
)
}
"use client"

export default function App(){
const data = useQuery(api.example.getData, {});

return (
...map data
)
}
example.ts
export const getData = query(
args: {},
async handler(ctx, args) {
const identity = await ctx.auth.getUserIdentity();

if(!identity)
throw new ConvexError("Authentication required");

const examples = await ctx.db.query("example").collect();
return PromiseAll(examples)
}
);
export const getData = query(
args: {},
async handler(ctx, args) {
const identity = await ctx.auth.getUserIdentity();

if(!identity)
throw new ConvexError("Authentication required");

const examples = await ctx.db.query("example").collect();
return PromiseAll(examples)
}
);
Nishil Faldu
Nishil Faldu9mo ago
Make sure you are using the exported query and I’d recommend wrapping your component which queries convex db in an Authenticated component @Ica
Ica
Ica9mo ago
Thanks. Managed to make it work as you suggested.
stefano
stefanoOP9mo ago
what do you mean by "Authenticated component"?
stefano
stefanoOP9mo ago
any example code you can point me at? it's still not clear to me why would I need to authenticate a component if I check the user's authentication at root page level. I use Clerk for my application, so I have a middleware.ts file where I defined the protected routes of my application:
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";

const isProtectedRoute = createRouteMatcher([
"/",
"/mypage(.*)",
]);

export default clerkMiddleware((auth, req) => {
if (isProtectedRoute(req)) auth().protect();

export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";

const isProtectedRoute = createRouteMatcher([
"/",
"/mypage(.*)",
]);

export default clerkMiddleware((auth, req) => {
if (isProtectedRoute(req)) auth().protect();

export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
Nishil Faldu
Nishil Faldu9mo ago
Depends on how you are checking it at the root level? Are you loading the children only if authenticated?
stefano
stefanoOP9mo ago
Exactly. If not authenticated for the specified routes in middleware.ts, the application redirects the user to the login page
Nishil Faldu
Nishil Faldu9mo ago
What’s the exact problem you are facing? Can you please elaborate?
stefano
stefanoOP9mo ago
No problem here, I'm just looking to understand if I need to wrap every component that fetches something from Convex DB with the Authenticated react module
Nishil Faldu
Nishil Faldu9mo ago
You definitely don’t need to use it. Like I have been using the above snippet I put, and that’s been just working fine. It’s only if sometimes (depending on how you write and use your query), the auth state or user is not set in time and authenticated component helps you put a nice loading state or whatever one wants
stefano
stefanoOP9mo ago
ok got it, thanks!

Did you find this page helpful?