Remove auth cookie in after hook #2048

Closed
opened 2026-03-13 09:23:04 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @ocherry341 on GitHub (Sep 30, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

{
    hooks: {
      after: [
        {
          matcher: () => {
            return true;
          },
          handler: createAuthMiddleware(async (ctx) => {
            throw new APIError(401);
          }),
        },
      ],
    },
}

Current vs. Expected behavior

Current:

Image

Expected:

There should be no set cookie

What version of Better Auth are you using?

1.3.16

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#68~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 15 18:06:34 UTC 2",
    "release": "6.8.0-65-generic",
    "cpuCount": 88,
    "cpuModel": "Intel(R) Xeon(R) CPU E5-2696 v4 @ 2.20GHz",
    "totalMemory": "125.76 GB",
    "freeMemory": "108.41 GB"
  },
  "node": {
    "version": "v22.18.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.3"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "15.5.4"
    },
    {
      "name": "react",
      "version": "^19"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.5"
    }
  ],
  "betterAuth": {
    "version": "^1.3.16",
    "config": {
      "user": {
        "additionalFields": {
          "credits": {
            "type": "number",
            "required": true,
            "defaultValue": 0,
            "input": false,
            "returned": true
          }
        }
      },
      "session": {
        "cookieCache": {
          "enabled": false
        }
      },
      "emailAndPassword": {
        "enabled": true,
        "autoSignIn": true
      },
      "socialProviders": {
        "google": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]",
          "redirectURI": "http://localhost:3000/api/auth/callback/google"
        }
      },
      "plugins": [
        {
          "name": "next-cookies",
          "config": {
            "id": "next-cookies",
            "hooks": {
              "after": [
                {}
              ]
            }
          }
        },
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "USER_ALREADY_EXISTS": "User already exists. Use another email.",
              "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself",
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users",
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users",
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users",
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]",
              "BANNED_USER": "You have been banned from this application",
              "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user",
              "NO_DATA_TO_UPDATE": "No data to update",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users",
              "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself"
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            }
          }
        },
        {
          "name": "waitlist",
          "config": {
            "id": "waitlist",
            "schema": {
              "waitlist": {
                "fields": {
                  "email": {
                    "type": "string",
                    "required": true,
                    "unique": true
                  },
                  "status": {
                    "type": "string",
                    "required": true,
                    "input": false,
                    "defaultValue": "pending"
                  },
                  "requestedAt": {
                    "type": "date",
                    "required": false,
                    "input": false
                  },
                  "processedAt": {
                    "type": "date",
                    "required": false,
                    "input": false
                  },
                  "processedBy": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "no action"
                    },
                    "required": false,
                    "input": false
                  }
                }
              }
            },
            "$ERROR_CODES": {
              "EMAIL_ALREADY_IN_WAITLIST": "email_already_in_waitlist",
              "DOMAIN_NOT_ALLOWED": "domain_not_allowed",
              "INVALID_ENTRY": "invalid_entry",
              "WAITLIST_FULL": "waitlist_full",
              "RATE_LIMIT_EXCEEDED": "rate_limit_exceeded",
              "WAITLIST_NOT_ENABLED": "waitlist_not_enabled",
              "UNAUTHORIZED": "unauthorized",
              "FORBIDDEN": "permission_denied",
              "WAITLIST_ENTRY_NOT_FOUND": "waitlist_entry_not_found"
            },
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            }
          }
        },
        {
          "name": "harmony-email",
          "config": {
            "id": "harmony-email",
            "schema": {
              "user": {
                "fields": {
                  "normalizedEmail": {
                    "type": "string",
                    "required": false,
                    "unique": true,
                    "input": false,
                    "returned": false
                  }
                }
              }
            },
            "hooks": {
              "before": [
                {},
                {}
              ]
            }
          }
        },
        {
          "name": "stripe",
          "config": {
            "id": "stripe",
            "endpoints": {},
            "schema": {
              "subscription": {
                "fields": {
                  "plan": {
                    "type": "string",
                    "required": true
                  },
                  "referenceId": {
                    "type": "string",
                    "required": true
                  },
                  "stripeCustomerId": {
                    "type": "string",
                    "required": false
                  },
                  "stripeSubscriptionId": {
                    "type": "string",
                    "required": false
                  },
                  "status": {
                    "type": "string",
                    "defaultValue": "incomplete"
                  },
                  "periodStart": {
                    "type": "date",
                    "required": false
                  },
                  "periodEnd": {
                    "type": "date",
                    "required": false
                  },
                  "trialStart": {
                    "type": "date",
                    "required": false
                  },
                  "trialEnd": {
                    "type": "date",
                    "required": false
                  },
                  "cancelAtPeriodEnd": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "seats": {
                    "type": "number",
                    "required": false
                  }
                }
              },
              "user": {
                "fields": {
                  "stripeCustomerId": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            }
          }
        }
      ]
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

A common requirement is to get the email and do something, but in scoial provders like google, it is impossible to get the email in the before hook.
However, in the after hook, the auth cookie cannot be modified. If the cookie cache is used, the session will persist even if the session is deleted in the database. Therefore, a method to rewrite the header or delete the session cookie is needed.

Originally created by @ocherry341 on GitHub (Sep 30, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ```typescript { hooks: { after: [ { matcher: () => { return true; }, handler: createAuthMiddleware(async (ctx) => { throw new APIError(401); }), }, ], }, } ``` ### Current vs. Expected behavior Current: <img width="1628" height="859" alt="Image" src="https://github.com/user-attachments/assets/271ca6a0-880c-40af-81fd-8e01adfa9839" /> Expected: There should be no set cookie ### What version of Better Auth are you using? 1.3.16 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#68~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 15 18:06:34 UTC 2", "release": "6.8.0-65-generic", "cpuCount": 88, "cpuModel": "Intel(R) Xeon(R) CPU E5-2696 v4 @ 2.20GHz", "totalMemory": "125.76 GB", "freeMemory": "108.41 GB" }, "node": { "version": "v22.18.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.3" }, "frameworks": [ { "name": "next", "version": "15.5.4" }, { "name": "react", "version": "^19" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.5" } ], "betterAuth": { "version": "^1.3.16", "config": { "user": { "additionalFields": { "credits": { "type": "number", "required": true, "defaultValue": 0, "input": false, "returned": true } } }, "session": { "cookieCache": { "enabled": false } }, "emailAndPassword": { "enabled": true, "autoSignIn": true }, "socialProviders": { "google": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]", "redirectURI": "http://localhost:3000/api/auth/callback/google" } }, "plugins": [ { "name": "next-cookies", "config": { "id": "next-cookies", "hooks": { "after": [ {} ] } } }, { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": "Failed to create user", "USER_ALREADY_EXISTS": "User already exists. Use another email.", "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself", "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions", "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users", "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users", "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions", "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users", "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]", "BANNED_USER": "You have been banned from this application", "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user", "NO_DATA_TO_UPDATE": "No data to update", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users", "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself" }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } } } }, { "name": "waitlist", "config": { "id": "waitlist", "schema": { "waitlist": { "fields": { "email": { "type": "string", "required": true, "unique": true }, "status": { "type": "string", "required": true, "input": false, "defaultValue": "pending" }, "requestedAt": { "type": "date", "required": false, "input": false }, "processedAt": { "type": "date", "required": false, "input": false }, "processedBy": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "no action" }, "required": false, "input": false } } } }, "$ERROR_CODES": { "EMAIL_ALREADY_IN_WAITLIST": "email_already_in_waitlist", "DOMAIN_NOT_ALLOWED": "domain_not_allowed", "INVALID_ENTRY": "invalid_entry", "WAITLIST_FULL": "waitlist_full", "RATE_LIMIT_EXCEEDED": "rate_limit_exceeded", "WAITLIST_NOT_ENABLED": "waitlist_not_enabled", "UNAUTHORIZED": "unauthorized", "FORBIDDEN": "permission_denied", "WAITLIST_ENTRY_NOT_FOUND": "waitlist_entry_not_found" }, "endpoints": {}, "hooks": { "after": [ {} ] } } }, { "name": "harmony-email", "config": { "id": "harmony-email", "schema": { "user": { "fields": { "normalizedEmail": { "type": "string", "required": false, "unique": true, "input": false, "returned": false } } } }, "hooks": { "before": [ {}, {} ] } } }, { "name": "stripe", "config": { "id": "stripe", "endpoints": {}, "schema": { "subscription": { "fields": { "plan": { "type": "string", "required": true }, "referenceId": { "type": "string", "required": true }, "stripeCustomerId": { "type": "string", "required": false }, "stripeSubscriptionId": { "type": "string", "required": false }, "status": { "type": "string", "defaultValue": "incomplete" }, "periodStart": { "type": "date", "required": false }, "periodEnd": { "type": "date", "required": false }, "trialStart": { "type": "date", "required": false }, "trialEnd": { "type": "date", "required": false }, "cancelAtPeriodEnd": { "type": "boolean", "required": false, "defaultValue": false }, "seats": { "type": "number", "required": false } } }, "user": { "fields": { "stripeCustomerId": { "type": "string", "required": false } } } } } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context A common requirement is to get the email and do something, but in scoial provders like google, it is impossible to get the email in the before hook. However, in the after hook, the auth cookie cannot be modified. If the cookie cache is used, the session will persist even if the session is deleted in the database. Therefore, a method to rewrite the header or delete the session cookie is needed.
GiteaMirror added the nextjs label 2026-03-13 09:23:04 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 30, 2025):

Hi @ocherry341! I'm Dosu and I’m helping the better-auth team.

You can remove the auth cookie in an after hook by calling the deleteSessionCookie(ctx) utility, which sets the session cookies with maxAge: 0 to clear them from the client. The context object (ctx) in Better Auth hooks exposes cookie manipulation methods like setCookie, so you can use this directly in your after hook handler. For example:

import { deleteSessionCookie } from "better-auth"; // adjust import path as needed

{
  hooks: {
    after: [
      {
        matcher: () => true,
        handler: async (ctx) => {
          deleteSessionCookie(ctx);
          throw new APIError(401);
        },
      },
    ],
  },
}

This approach ensures the session cookie is cleared in the response, even after a failed authentication. If your integration exposes ctx.res or similar, you can also use cookie utilities directly in the handler. For more details, see the cookie handling implementation.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Sep 30, 2025): <!-- Greeting --> Hi @ocherry341! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> You can remove the auth cookie in an after hook by calling the `deleteSessionCookie(ctx)` utility, which sets the session cookies with `maxAge: 0` to clear them from the client. The context object (`ctx`) in Better Auth hooks exposes cookie manipulation methods like `setCookie`, so you can use this directly in your after hook handler. For example: ```typescript import { deleteSessionCookie } from "better-auth"; // adjust import path as needed { hooks: { after: [ { matcher: () => true, handler: async (ctx) => { deleteSessionCookie(ctx); throw new APIError(401); }, }, ], }, } ``` This approach ensures the session cookie is cleared in the response, even after a failed authentication. If your integration exposes `ctx.res` or similar, you can also use cookie utilities directly in the handler. For more details, see the [cookie handling implementation](https://github.com/better-auth/better-auth/blob/f6cbdcc84ee5d2971fdcc8b23ff7c174f88cf45b/packages/better-auth/src/cookies/index.ts). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/0ba04b8e-cfb8-476c-8633-ea63e9fd76b4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/0ba04b8e-cfb8-476c-8633-ea63e9fd76b4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/0ba04b8e-cfb8-476c-8633-ea63e9fd76b4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/0ba04b8e-cfb8-476c-8633-ea63e9fd76b4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/0ba04b8e-cfb8-476c-8633-ea63e9fd76b4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/0ba04b8e-cfb8-476c-8633-ea63e9fd76b4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/0ba04b8e-cfb8-476c-8633-ea63e9fd76b4?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4983)
Author
Owner

@himself65 commented on GitHub (Sep 30, 2025):

As the bot said, you should call deleteSessionCookie manually. I think deleting cookies in advance is not a good idea.

@himself65 commented on GitHub (Sep 30, 2025): As the bot said, you should call `deleteSessionCookie` manually. I think deleting cookies in advance is not a good idea.
Author
Owner

@himself65 commented on GitHub (Jan 20, 2026):

I think this is a issue in our endpoint API

d5a88df986/packages/better-auth/src/api/to-auth-endpoints.ts (L276-L311)

@himself65 commented on GitHub (Jan 20, 2026): I think this is a issue in our endpoint API https://github.com/better-auth/better-auth/blob/d5a88df98653a0d65219715af4e86c2949ec5e4f/packages/better-auth/src/api/to-auth-endpoints.ts#L276-L311
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2048