Authentication & Security

Authentication & Security

Authentication is a vital part in protecting your app from malicious usage. In this section we'll go over how to protect different parts of the UploadThing flow.

⚠️

Do not protect the entire /api/uploadthing route from being called by unauthenticated users. The endpoint is called as a webhook by our server and thus must be publically available.

Protecting the endpoint from spoofing

The callback request is like a webhook that is called by UploadThing when your file has been uploaded to the storage provider. The callback data is signed (HMAC SHA256) using the API key that uploaded the file. Since v6.7 of the Uploadthing SDK, the callback data is automatically verified before executing the callback. There is no additional work needed to protect the callback endpoint other than making sure you're on a version ^6.7 to ensure your endpoint is appropriately protected.

Protecting unauthenticated users from uploading files

You can protect unauthenticated users from uploading files via the .middleware() function in each file route. This makes it trivial to protect some file routes, and keep some public.

Using your favorite authentication provider (or self-roll if that's your thing), retrieve the current user's session from the incoming request. If it's not valid, you can throw an error which will terminate the upload flow. In the following example, we have a public file route that is protected by rate limiting, and a protected route that allows any authenticated user to upload files:

~/server/uploadthing
import { auth } from "auth";
 
import { createUploadthing, UploadThingError } from "uploadthing/server";
 
import { RateLimit } from "~/lib/ratelimit";
 
const ratelimiter = new RateLimit({
  /** rules */
});
 
export const uploadRouter = {
  publicRoute: f({ image: {} })
    .middleware(async ({ req }) => {
      const limit = await ratelimiter.verify(req);
      if (!limit.ok) {
        throw new UploadThingError("Rate limit exceeded");
      }
 
      return {};
    })
    .onUploadComplete(() => {
      /** ... */
    }),
 
  privateRoute: f({ image: {} })
    .middleware(async ({ req }) => {
      const session = await auth(req);
      if (!session) {
        throw new UploadThingError("You need to be logged in to upload files");
      }
 
      return { userId: session.user.id };
    })
    .onUploadComplete(() => {
      /** ... */
    }),
};

By throwing an UploadThingError, the error message is automatically sent down to the client. If throwing other errors, you need an errorFormatter to control what is sent down to the client.