Multi-Access

Every auth path resolves to the same userId. They compose naturally because userId is the single shared anchor across all access patterns.

Default app pattern

In app queries, mutations, and actions, use auth.ctx() once and then read the resolved user from ctx.auth.

// convex/functions.ts
export const authQuery = customQuery(query, auth.ctx());
export const myGroups = authQuery({
  args: {},
  handler: async (ctx) => {
    return await auth.member.list(ctx, {
      where: { userId: ctx.auth.userId },
    });
  },
});

Raw HTTP fallback

Most apps do not need auth.http.context(...). Keep it for raw httpAction handlers that intentionally accept either a browser session or an API key in the same endpoint.

http.route({
  path: "/api/data",
  method: "GET",
  handler: httpAction(async (ctx, request) => {
    const authContext = await auth.http.context(ctx, request, {
      optional: true,
    });
    if (authContext.userId === null) {
      return new Response(JSON.stringify({ error: "Unauthorized" }), {
        status: 401,
      });
    }
    const data = await ctx.runQuery(internal.data.forUser, {
      userId: authContext.userId,
    });
    return Response.json(data);
  }),
});

How each access pattern resolves

How the user authenticatedHow userId is available
Browser (password, email, passkey, OAuth)ctx.auth.userId via auth.ctx()
Group SSO (OIDC/SAML)Same as browser - SSO completes as a session
Device flow (RFC 8628, CLI/TV)Same as browser - device poll returns session tokens
API key (machine/automation)ctx.key.userId or auth.http.context(ctx, request).userId

Composing primitives

// Works for any authenticated caller
async function getMyGroups(ctx: any, userId: string) {
  const { items } = await auth.member.list(ctx, { where: { userId } });
  return items;
}

// Browser session
const handler = authQuery({
  args: {},
  handler: async (ctx) => {
    return getMyGroups(ctx, ctx.auth.userId);
  },
});

// API key HTTP endpoint
const apiHandler = auth.http.action(async (ctx) => {
  return getMyGroups(ctx, ctx.key.userId);
});

// Any HTTP action (session or API key)
const flexHandler = httpAction(async (ctx, request) => {
  const authContext = await auth.http.context(ctx, request);
  return Response.json(await getMyGroups(ctx, authContext.userId));
});