wobsoriano / vue-clerk

Community Clerk Vue SDK.
https://vue-clerk.com
MIT License
156 stars 14 forks source link

How do I use useUser on the server side #67

Closed simonjcarr closed 4 months ago

simonjcarr commented 4 months ago

Thanks for this library I love that I can now use Clerk in Nuxt.

My only issue at the moment is that I am only able to access the Clerk user on the front end with useUser.

When I use a useFetch to pull data from the backend, I need to be able to access user direct from Clerk so I can verify the user before sending data back to the front end.

With the following code, I get the error This composable can only be used when the Vue Clerk plugin is installed

import { useUser } from 'vue-clerk'
export default defineEventHandler(async (event) => {
  const user = useUser()
   console.log("clerk user", user)
})

Update: I notice from another issue that is now closed a user was told that vue-clerk only works on the client. If this is the case how do we secure a backend in nuxt. I can not trust the front end to send a userId to the backend, that could be faked by a malicious actor.

I am assuming I am missing something here that other people have already solved, but how do I get secure access to the clerk user on the backend?

simonjcarr commented 4 months ago

I have found a solution. There is a __session cookie set that contains the clerk JWT. This can be verified with @clerk/clerk-sdk-node

Here is a function I have setup on the server side, that takes an event from one of my API's, validates the token and returns both the clerk userId (JWT sub) and the local user I have in my database for each clerk user.

 import { verifyToken } from '@clerk/clerk-sdk-node'
import { db } from '~/server/db';
import { H3Event } from 'h3'
import { eq } from 'drizzle-orm';
import { users } from '../db/schema';
export default async function (event: H3Event) {
    const cookies = parseCookies(event)
    const key = process.env.CLERK_SECRET_KEY || '';
    try {
        const payload = await verifyToken(cookies.__session, { secretKey: key });
        const dbUser = await db.query.users.findFirst({
            where: eq(users.clerkId, payload.sub || ''),
        })
        return { clerkId: payload.sub, userId: dbUser?.id }
    } catch (error) {
        throw new Error('Auth Token is not valid')
    }
}

Now in my API routes, I can simply do this

import validateToken from '~/server/auth/validateToken'
export default defineEventHandler(async (event) => {
  const { clerkId, userId } = await validateToken(event)
  ....
})

Hopefully this will help someone else out and perhaps could be added to the Nuxt Guide

wobsoriano commented 4 months ago

Hey! If you're using Nuxt, you might be interested in this template that uses vue-clerk and h3-clerk - https://github.com/wobsoriano/nuxt-clerk-template

See how the plugin was installed here - supports SSR!

Then you can do this in your route middleware

import { useAuth } from 'vue-clerk'

export default defineNuxtRouteMiddleware((to) => {
  const { isSignedIn } = useAuth()

  const protectedPages = ['dashboard', 'organization']
  const publicPages = ['sign-in', 'sign-up']

  const isProtectedPage = protectedPages.includes(to.name as string)
  const isPublicPage = publicPages.includes(to.name as string)

  if (isSignedIn.value && isPublicPage)
    return navigateTo('/dashboard')

  if (!isSignedIn.value && isProtectedPage)
    return navigateTo('/sign-in')
})
simonjcarr commented 4 months ago

Hi @wobsoriano I am already using that middleware and protecting my pages with it, but I don't see how that gives me access to the clerk user in a function running on the server?

wobsoriano commented 4 months ago

Oh my bad, I actually thought the code you posted are route middlewares! Im blind lol

Well in that case, if you're using h3-clerk, you can use the auth context to access the user:

import { clerkClient, getAuth } from 'h3-clerk'

export default eventHandler((event) => {
  const auth = getAuth(event)
  // or just get it directly from `event.context.auth`

  if (!auth.userId) {
    setResponseStatus(event, 403)
    return
  }

  return clerkClient.users.getUser(auth.userId)
})
simonjcarr commented 4 months ago

Thank you