#Authenticate without hook
15 messages · Page 1 of 1 (latest)
Hi @viscid briar. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
To authenticate mutation calls with client.mutation without using hooks, you have several options depending on your specific use case:
Using API Keys for Server-to-Server Authentication
For server environments like Lambda functions or other backend services, you can implement a simple API key authentication:
import { ConvexClient } from 'convex/browser';
const client = new ConvexClient(CONVEX_URL);
await client.mutation(api.tasks.dumpData, {
data,
apiKey: "your-shared-secret" // Pass your API key as an argument
});
On the Convex side, validate this key in your mutation:
export const dumpData = mutation({
args: {
data: v.any(),
apiKey: v.string()
},
handler: async (ctx, args) => {
// Validate the API key
if (args.apiKey !== process.env.API_KEY) {
throw new Error("Invalid API key");
}
// Continue with authenticated operation
// ...
},
});
This approach is recommended for server-to-server communication as mentioned in the Discord support thread.
For Next.js Server Actions and Route Handlers
If you're using Next.js server components, you can use the fetchMutation function with an auth token:
import { fetchMutation } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
import { convexAuthNextjsToken } from "@convex-dev/auth/nextjs/server";
// In a server action or route handler
const token = await convexAuthNextjsToken();
await fetchMutation(
api.files.append,
{
// your mutation arguments
},
{
token, // Pass the auth token here
}
);
This approach is documented in Auth > Authz > Server-side authentication in Next.js.
Using HTTP Client with Authentication
For other server environments where you have access to authentication tokens:
import { ConvexHttpClient } from "convex/browser";
const client = new ConvexHttpClient(CONVEX_URL);
// Set the auth token if you have one
if (token) {
client.setAuth(token);
}
// Now your mutations will be authenticated
await client.mutation(api.myFunctions.doSomething, {
// mutation arguments
});
Mutation Implementation with Auth Check
Regardless of how you call the mutation, your Convex function should validate the authentication:
export const doSomething = mutation({
args: { /* your args */ },
handler: async (ctx, args) => {
// Check authentication
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthenticated call to mutation");
}
// Optionally get the user from your database
const user = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) {
throw new Error("User not found");
}
// Continue with authenticated operation
// ...
},
});
This pattern is shown in the Authentication Best Practices documentation.
Remember that all exported mutation functions are publicly accessible to anyone with your Convex URL, so proper authentication checks within your functions are essential.
GitHub Copilot:
I'll help you understand how to authenticate mutation calls with client.mutation without using hooks in Convex.
Direct Authentication with ConvexHttpClient
When working outside of React components (like in server environments), you can use the ConvexHttpClient with authentication:
import { ConvexHttpClient } from "convex/browser";
const client = new ConvexHttpClient(CONVEX_URL);
// If you have an auth token from your auth provider
if (token) {
client.setAuth(token);
}
// Now your mutations will be authenticated
await client.mutation(api.myFunctions.doSomething, {
// mutation arguments
});
Next.js Server-Side Authentication
For Next.js server components, route handlers, or server actions, you can use the fetchMutation function with an auth token:
import { fetchMutation } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
import { convexAuthNextjsToken } from "@convex-dev/auth/nextjs/server";
// In a server action or route handler
const token = await convexAuthNextjsToken();
await fetchMutation(
api.files.append,
{
// your mutation arguments
},
{
token, // Pass the auth token here
}
);
API Key Authentication for Server-to-Server
For Lambda functions or other backend services, you can implement API key authentication:
import { ConvexClient } from 'convex/browser';
const client = new ConvexClient(CONVEX_URL);
await client.mutation(api.tasks.dumpData, {
data,
apiKey: "your-shared-secret" // Pass your API key as an argument
});
On the Convex side, validate this key in your mutation:
export const dumpData = mutation({
args: {
data: v.any(),
apiKey: v.string()
},
handler: async (ctx, args) => {
// Validate the API key
if (args.apiKey !== process.env.API_KEY) {
throw new Error("Invalid API key");
}
// Continue with authenticated operation
},
});
Authentication Check in Mutations
Regardless of how you call the mutation, your Convex function should validate authentication:
export const doSomething = mutation({
args: { /* your args */ },
handler: async (ctx, args) => {
// Check authentication
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthenticated call to mutation");
}
// Optionally get the user from your database
const user = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) {
throw new Error("User not found");
}
// Continue with authenticated operation
},
});
Remember that all exported mutation functions are publicly accessible to anyone with your Convex URL, so proper authentication checks within your functions are essential.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@viscid briar Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Let me check the knowledge base for you again. One moment! :books:
Sorry @viscid briar, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?
@turbid moss How to get convex auth token
Let me check the knowledge base for you again. One moment! :books:
I'll explain how to get a Convex authentication token in different contexts.
In Next.js Server Components/Actions
For Next.js server components, server actions, or route handlers, you can use the convexAuthNextjsToken function:
import { convexAuthNextjsToken } from "@convex-dev/auth/nextjs/server";
import { fetchMutation } from "convex/nextjs";
import { api } from "@/convex/_generated/api";
// Get the token
const token = await convexAuthNextjsToken();
// Use it with fetchMutation or fetchQuery
await fetchMutation(
api.tasks.create,
{ /* your args */ },
{ token }
);
This approach is documented in Auth > Authz > Server-side authentication in Next.js.
With Authentication Providers
The method to get a token depends on your authentication provider:
With Clerk
import { auth } from "@clerk/nextjs/server";
export async function getAuthToken() {
return (await (await auth()).getToken({ template: "convex" })) ?? undefined;
}
With Auth0
import { getSession } from '@auth0/nextjs-auth0';
export async function getAuthToken() {
const { accessToken } = await getSession();
return accessToken;
}
These examples are from the Next.js Server Rendering documentation.
With NextAuth.js
For NextAuth.js, you need to create and sign a JWT in your session callback:
import { SignJWT, importPKCS8 } from "jose";
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...other config
callbacks: {
async session({ session }) {
const privateKey = await importPKCS8(
process.env.CONVEX_AUTH_PRIVATE_KEY!,
"RS256",
);
const convexToken = await new SignJWT({
sub: session.userId,
})
.setProtectedHeader({ alg: "RS256" })
.setIssuedAt()
.setIssuer(CONVEX_SITE_URL)
.setAudience("convex")
.setExpirationTime("1h")
.sign(privateKey);
return { ...session, convexToken };
},
},
});
This approach is detailed in the [Convex Adapter for Auth.js Setup Guide](<https://stack.convex.dev/nextauth-adapter#issue-the-jwt>).
## For Server-to-Server Authentication
For server-to-server communication, you typically use a shared secret approach rather than tokens:
```javascript
// Set up a shared secret in both environments
// Then pass it as an argument to your mutation
await client.mutation(api.tasks.dumpData, {
data,
apiKey: "your-shared-secret"
});
This pattern was mentioned in our previous conversation about authenticating without hooks.