[Regression on closed #553] Still no logout endpoint — the httpOnly `access_token` cookie cannot be cleared by any client
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 59/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- authentication
Research direction
Start with src/auth/auth.controller.ts and read how githubCallback() sets the access_token cookie, then check src/auth/strategies/jwt.strategy.ts to confirm how requests authenticate from cookies. Add a logout route that clears the cookie with matching attributes, and cover it in auth.controller.spec.ts and test/auth.e2e-spec.ts. Done means the cookie is cleared and a cookie-only request to /auth/me returns 401 afterward.
Written by the indexing model from the issue text.
Description
Location
src/auth/auth.controller.ts:61-67—AuthController.githubCallback()sets the cookiesrc/auth/auth.controller.ts— nologoutroute exists anywhere in the controllersrc/auth/strategies/jwt.strategy.ts:14-33—cookieExtractoraccepts that cookie on every request
Problem
The OAuth callback issues a session cookie:
res.cookie('access_token', accessToken, {
httpOnly: true,
secure: isProd,
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000,
path: '/',
});
grep -rni 'logout\|clearCookie' src/ returns nothing. There is no POST /auth/logout, no res.clearCookie('access_token') call, and AuthController exposes only github, github/callback, handoff, exchange and me.
Because the cookie is httpOnly, frontend JavaScript cannot delete it either — that is the entire point of the flag. The only parties who can clear it are the server (which offers no route) and the user manually, through browser devtools or by clearing site data.
This is closed issue #553, unchanged.
Why it matters
JwtStrategy is configured to accept the token from either the Authorization header or the cookie (jwt.strategy.ts:42-45). So "signing out" in the frontend — discarding the bearer token it received from POST /auth/handoff — does not end the session at all: every subsequent request from that browser still carries the cookie, cookieExtractor finds it, and the user remains fully authenticated for the cookie's entire 7-day maxAge.
Concretely:
- On a shared or public machine, a user who clicks "Sign out" and walks away leaves a live session behind. The next person to open the site is authenticated as them and can read
GET /auth/me,GET /users, and — depending on the roles on that account — reach the money-moving routes under/bounties,/escrowand/milestones. - There is also no JWT revocation mechanism (closed #10 / #341, still unaddressed), so there is no server-side way to invalidate the token even administratively. The cookie plus the absence of revocation means a session genuinely cannot be ended for seven days by any means available to the product.
- The cookie's
maxAgeis a hard-coded 7 days independent ofJWT_EXPIRES_IN(closed #552), so shortening the JWT lifetime does not shorten the window either — it just produces a cookie that is present but rejected, with no way to remove it.
Reproduction / failure path
- Complete GitHub OAuth in a browser; confirm the
access_tokencookie is set (DevTools -> Application -> Cookies). - In the frontend, sign out (or simply clear
localStorage/sessionStorage— whatever holds the bearer token). - With no
Authorizationheader, requestGET /api/auth/mefrom the same browser. - Observed: HTTP 200 with
{ userId, username }— still authenticated. Expected: 401. - Confirm there is no way to fix this from the client:
document.cookie = 'access_token=; Max-Age=0'has no effect, because the cookie ishttpOnly.
Suggested fix
Add an authenticated logout route to AuthController that clears the cookie with the same attributes it was set with (Express only matches path/domain/sameSite/secure when clearing; a mismatch silently leaves the cookie in place):
@Post('logout')
@HttpCode(204)
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
logout(@Res({ passthrough: true }) res: Response) {
const isProd = this.configService.get('env', { infer: true }) === 'production';
res.clearCookie('access_token', {
httpOnly: true,
secure: isProd,
sameSite: 'lax',
path: '/',
});
}
Consider not requiring JwtAuthGuard on this route (or catching the 401): a user whose token has already expired should still be able to clear the stale cookie, and a logout that 401s is a confusing dead end. A separate unguarded POST /auth/logout that only ever clears the cookie is harmless — it grants nothing.
Derive the cookie's maxAge from the configured JWT_EXPIRES_IN rather than hard-coding 7 days (closed #552), so the cookie and the token it carries expire together.
Add an auth.controller.spec.ts case asserting clearCookie is called with the matching attribute set, and a test/auth.e2e-spec.ts case asserting that GET /auth/me returns 401 after POST /auth/logout when the request carries only the cookie.
- Dominant language
- TypeScript
- Stars
- 2
- Forks
- 90
- Avg merge
- 31m
- Merged PRs (30d)
- 20
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from MergeFi/backend
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
bug security
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
bug testing
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
bug security
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
zapcooking/frontend#946 ·
Maintainers usually reply within 1 day
-
enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
WiliamRosa/cosmos-semantic-platform#40 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
liberation-data/drivine#733 · 1 comment ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
makecindy/cindy#10062 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day