Why a sign out endpoint should be idempotent
A sign out endpoint should return the same success response whether the presented session was live, already revoked, expired, or never existed. The caller asked for one outcome: this token must stop working. That outcome holds in all four cases, so all four are a success. Returning an error for the second call breaks retries, and returning a different message for an unknown token tells an anonymous caller which tokens are real.
Sign out requests an end state
RFC 9110 defines a request as idempotent when sending it several times has the same intended effect on the server as sending it once. Revoking a session fits that definition. The first request marks the session revoked. A second request finds nothing left to change. The server holds the same state after either one.
A 409 or 404 on the second request treats sign out as a transition that must start from a signed in state. The server then has to report on the starting state, and every client has to handle a failure that leaves the user exactly where they wanted to be. Creating a resource is different: a second sign up with the same email should fail, because a second account is a second effect.
A different response confirms which tokens are valid
Sign out is reachable without proof of identity beyond the token itself. If a live token returns 200 and an unknown token returns 404, the endpoint answers the question "is this session token valid?" for anyone who asks. Session tokens are long random values, so guessing one is not practical, but a token copied from a log line, a proxy capture, or a shared device can be checked silently before it is used. One uniform response removes that check.
A password reset form follows the same rule when it shows one confirmation whether or not the email belongs to an account.
Retries and repeated clicks need the same answer
A sign out request can succeed on the server while the response is lost on a mobile connection. The client retries. With an idempotent endpoint the retry returns success and the app moves to the signed out screen. With a strict endpoint the retry returns an error for a user who is already signed out, and the client has to decide whether that error means success.
The same thing happens without a network fault. A user clicks the button twice. Two tabs sign out within the same second. A session reaches its expiry a moment before the click. Hiding the button in the interface does not cover these cases, because the interface holds a copy of the session state that can be stale by the time the request arrives.
What Own Auth does
auth.signOut takes a session token and resolves to nothing. For a live session it sets revokedAt, records the reason user_logout, and writes user.signed_out and session.revoked audit events. For an unknown token or a session that is already revoked it returns before writing anything, so a repeated call adds no duplicate audit events and does not move the original revocation time.
The package's HTTP handler follows the same rule at POST /sign-out. It calls auth.signOut only when the request carries a token, then returns a success body and clears the session cookie in every case, including a request with no token at all. A route written by hand needs the same two properties: a missing token is not an error, and the cookie is cleared whatever the lookup found.
import { clearSessionCookie, readSessionToken } from "own-auth";
import { auth } from "@/lib/auth";
export async function POST(request: Request) {
const { token } = readSessionToken(request);
if (token) await auth.signOut(token);
return Response.json(
{ success: true },
{
headers: {
"Set-Cookie": clearSessionCookie(new URL(request.url)),
},
},
);
}A storage failure still surfaces as a server error. Idempotent means repeated requests are safe. It does not mean the endpoint reports success when the revocation could not be saved.
Read session state from a session endpoint
A client that needs to know whether it holds a live session should ask that question directly. auth.getCurrentSession returns the session and user for a valid token and null for one that is unknown, expired, revoked, or attached to a disabled user. The caller already holds the token it is asking about, so the answer discloses nothing about any other session.
Signing in twice without signing out creates two sessions, each with its own token, and signing out with one token revokes only that one. An account screen can show every session with auth.listSessions, which requires a signed in user.
Revoking a chosen session returns errors
auth.revokeSession is stricter because its caller is authenticated and names a target. It first verifies the caller's own session token and fails with invalid_session and HTTP 401 when that token is not valid. It then looks for the requested session ID among the caller's own sessions and fails with HTTP 404 when it is not there. A target that is already revoked is returned unchanged, so repeating the same request is still safe.
The difference between the two methods is who is asking. A signed in user managing a device list can be told that a session ID is not theirs. An anonymous request holding only a token gets one answer, and after that answer the token does not work.