Better Auth Api Key Sessions

what is the correct way to use the api key plugin to create a session for the user? Basically we have a nextjs application and want to generate api keys which the user can access our application (dashboard) with. Once api key is provided in login we would somehow need to create a session.
31 Replies
Ping
Ping•3w ago
Right now, the built-in functionality with sessions & Api-keys works by mocking a session any time a request comes with headers that satisfies x-api-key or however it is configured. Mocking a session will only get you so far, and I believe what you desire requires more than to mock a session. You may need to build a custom plugin to do this. Essentially a way to in-take an api-key, and create a new session in your DB and return that session to the user. I recommend looking through the better-auth codebase and seeing if any of the existing plugins implemnets something similar to this, to get an idea on how you can build this plugin.
jlorezz
jlorezzOP•2w ago
mocking session as in like creatring a jwt?
Ping
Ping•2w ago
Nope, we just make a fake user object with a fake name, fake email, etc
jlorezz
jlorezzOP•2w ago
Is there a way to create a fake session as well? U just do it directly over db or is there some logic from better auth ?
Ping
Ping•2w ago
Hey sorry I was wrong. We generate a mock session, not user.
jlorezz
jlorezzOP•2w ago
but a session is linked to a user no?
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
image: text("image"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
});

export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
});
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
image: text("image"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
});

export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
});
at least
Ping
Ping•2w ago
Yeah, it's linked to the user of the key
jlorezz
jlorezzOP•2w ago
that's what it generated for me. so ur essentially also generating a user for each api key?
Ping
Ping•2w ago
Hold on, I need to re-read the issue here, just to understand what's going on 😅
jlorezz
jlorezzOP•2w ago
i am using trpc. for our api.
Ping
Ping•2w ago
No, an api-key must always have a user in the first place. So the user would generate said key, and when that key is placed in headers, and goes through any auth endpoints, it would generate a mock session.
jlorezz
jlorezzOP•2w ago
.mutation(async ({ ctx, input }) => {
const now = new Date();
// Create a mock user
const mockUser = {
id: uuidv4(),
name: input.username || "Mock User",
image: null,
createdAt: now,
updatedAt: now,
};

// Insert the mock user into the database
const [createdUser] = await db.insert(user).values(mockUser).returning();

const apiKey = await auth.api.createApiKey({
body: {
name: input.username || "My API Key",
expiresIn: 60 * 60 * 24 * 365,
prefix: input.tier || "free",
remaining: 100,
refillAmount: 100,
refillInterval: 60 * 60 * 24 * 7,
metadata: {
tier: input.tier || "free",
},
rateLimitTimeWindow: 1000 * 60 * 60 * 24,
rateLimitMax: 100,
rateLimitEnabled: true,
userId: createdUser.id,
},
});

if (!apiKey) {
throw new Error("Failed to create API key");
}

return {
...apiKey,
userId: createdUser.id,
};
}),
.mutation(async ({ ctx, input }) => {
const now = new Date();
// Create a mock user
const mockUser = {
id: uuidv4(),
name: input.username || "Mock User",
image: null,
createdAt: now,
updatedAt: now,
};

// Insert the mock user into the database
const [createdUser] = await db.insert(user).values(mockUser).returning();

const apiKey = await auth.api.createApiKey({
body: {
name: input.username || "My API Key",
expiresIn: 60 * 60 * 24 * 365,
prefix: input.tier || "free",
remaining: 100,
refillAmount: 100,
refillInterval: 60 * 60 * 24 * 7,
metadata: {
tier: input.tier || "free",
},
rateLimitTimeWindow: 1000 * 60 * 60 * 24,
rateLimitMax: 100,
rateLimitEnabled: true,
userId: createdUser.id,
},
});

if (!apiKey) {
throw new Error("Failed to create API key");
}

return {
...apiKey,
userId: createdUser.id,
};
}),
this was my initial approach. this would for example be a createApiKey endpoint this would then initially generrate a user in the database as well as the api key. i was looking into the createContext as i am not sure how performance wise it would be to validate and check a valid api key each time a endoint is called. seems like a wrong approach. that's why i was thinking of going a session direction. i am not sure if that's a thing tho. it generated my a table when i added the apikey plugin also for sessions that;s why i was a bit confused.. what do u think is the correct approach? (what does betterauth recommend)? how do you generate this mock "session"?
Ping
Ping•2w ago
Hey do you mind if I ask a few questions so that I fully understand your idea?
jlorezz
jlorezzOP•2w ago
yes. sure
Ping
Ping•2w ago
How do users sign-up? Do you make the API-keys and manually hand them out?
jlorezz
jlorezzOP•2w ago
i create them manually for now. this will be moved to our website where people can login and generate a said key.
Ping
Ping•2w ago
So the idea is to use the key which you generate, and then allow them to use that as a way of signing up?
jlorezz
jlorezzOP•2w ago
yep. basically a desktop app they enter the api key and have access.
Ping
Ping•2w ago
There isn't any Better-Auth recommended way to go about this yet. Let me do some testing on my end, and maybe I can come up with a solution.
jlorezz
jlorezzOP•2w ago
would mean the world. i saw a couple other users also ask for somethign similar for session based api keys i would also need the user object for a specfici api key or example we are doing logic where we need to reference and do some db relation stuff.
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
if (!ctx.apiKey) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "API key required",
cause: "No API key provided",
});
}

const { valid, error } = await ctx.auth.api.verifyApiKey({
body: {
key: ctx.apiKey,
},
});

if (!valid) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Invalid API key",
cause: error || "API key verification failed",
});
}

return next({
ctx: {
...ctx,
// Keep the verified API key in context
apiKey: ctx.apiKey,
},
});
});
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
if (!ctx.apiKey) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "API key required",
cause: "No API key provided",
});
}

const { valid, error } = await ctx.auth.api.verifyApiKey({
body: {
key: ctx.apiKey,
},
});

if (!valid) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Invalid API key",
cause: error || "API key verification failed",
});
}

return next({
ctx: {
...ctx,
// Keep the verified API key in context
apiKey: ctx.apiKey,
},
});
});
this was my initial approach. though this works. it doesn't relal work with the usecase. i would need to either search and get the user based on the key provided on each request to our api endpoint or create a session which gets created when the user provids a valid api key. my initial approach was to use auth.api.getApiKey to get information or the direct api key and do manual db search and get the user object based off that. but then i saw getApiKey does not reutnr the key..
Ping
Ping•2w ago
it's kind of rough to use the api-key plugin, since it primarily works around the assumption that a user already exists. In this case, we're trying to create a new user by using a key, which goes against the assumptions of this plugin.
jlorezz
jlorezzOP•2w ago
mhm. so what exactly would u say is the correct way to use this plugin? i could make users sign up. let me ask this.
Ping
Ping•2w ago
I don't think you can. I think you need make a new system for this, this plugin won't help, or even if it could, it would be a lot of hacks/work-arounds.
jlorezz
jlorezzOP•2w ago
how is the api key plugin recommended to be used with the sessions etc? system as in resture my initial flow of how i handle users and access to my application?
Ping
Ping•2w ago
Api keys in general are not for user sign up purposes, they're generally to access resources for example. In our case, the api-key plugin checks if you have a session, then creates a new API key and links it to that user. Meaning every existing key must link to a user.
jlorezz
jlorezzOP•2w ago
ah i see. so i would need to create a session with jwt manually on my api?
Ping
Ping•2w ago
I have a potential idea, it's a work-around the current auth system, but may just be what you need if you want to use Better-auth.
jlorezz
jlorezzOP•2w ago
i really love better auth so definately want to switch either i find a way to keep the lightweight access key setup or i will have to implement a more "sophisticated" auth flow.
Ping
Ping•2w ago
Give me a moment, it might take a bit to explain.
jlorezz
jlorezzOP•2w ago
currently its just a api. though i will add a dashboard as well so technically users could sign up there. word. tytxy
Ping
Ping•2w ago
Basically you'll need a system of your own to add users to your app. You new "system" will create a new row in user and set the email field to the API key that you would randomly generate. Then, it will create a row in the account table, and set the password to be the API key as well. Now, in your sign-up/in code, all you do, is call the authClient.signIn.email and provide the email as the API key the user will enter, and same with the password. I hope that makes sense @jlorezz

Did you find this page helpful?