Skip to content

Commit

Permalink
Expire auth token and PKCE cookies (#994)
Browse files Browse the repository at this point in the history
Get the expiration date from the auth token and set the cookie to expire
when the token itself expires.

PKCE sessions are slightly different. In some contexts, we do not start
the session itself until a middle point in the flow, like with email
verification, the session starts on the auth server once the
verification token is sent to the server, not when the email is sent
by the server to the email recipient. Since the verification token has a
lifespan of 24 hours at the moment, the expiration is longer than the
PKCE session lifespan which is currently 10 minutes. So instead of
trying to chase the smallest window of time we can, we set it to a
reasonable level: one week after which the PKCE verifier cookie is
expired, requiring whatever flow has begun to be retried.
  • Loading branch information
scotttrinh authored May 3, 2024
1 parent a108c80 commit 385949c
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 256 deletions.
120 changes: 39 additions & 81 deletions packages/auth-express/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ export class ExpressAuth {
return Auth.checkPasswordResetTokenValid(resetToken);
};

private createVerifierCookie = (res: ExpressResponse, verifier: string) => {
const expires = new Date(Date.now() + 1000 * 60 * 24 * 7); // In 7 days
res.cookie(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
path: "/",
sameSite: "strict",
expires,
});
};

private createAuthCookie = (res: ExpressResponse, authToken: string) => {
const expires = Auth.getTokenExpiration(authToken);
res.cookie(this.options.authCookieName, authToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
expires: expires ?? undefined,
});
};

getSession = (req: ExpressRequest) => {
const authCookie = req.cookies[this.options.authCookieName];

Expand Down Expand Up @@ -265,13 +285,7 @@ export class ExpressAuth {
const pkceSession = await this.core.then((core) =>
core.createPKCESession()
);
res.cookie(
this.options.pkceVerifierCookieName,
pkceSession.verifier,
{
httpOnly: true,
}
);
this.createVerifierCookie(res, pkceSession.verifier);
res.redirect(
pkceSession.getOAuthUrl(
provider,
Expand Down Expand Up @@ -316,10 +330,7 @@ export class ExpressAuth {
}
const isSignUp = searchParams.get("isSignUp") === "true";
const tokenData = await (await this.core).getToken(code, verifier);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "lax",
});
this.createAuthCookie(res, tokenData.auth_token);
res.clearCookie(this.options.pkceVerifierCookieName);

req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
Expand All @@ -345,9 +356,7 @@ export class ExpressAuth {
const pkceSession = await this.core.then((core) =>
core.createPKCESession()
);
res.cookie(this.options.pkceVerifierCookieName, pkceSession.verifier, {
httpOnly: true,
});
this.createVerifierCookie(res, pkceSession.verifier);
res.redirect(pkceSession.getHostedUISigninUrl());
} catch (err) {
next(err);
Expand All @@ -362,9 +371,7 @@ export class ExpressAuth {
const pkceSession = await this.core.then((core) =>
core.createPKCESession()
);
res.cookie(this.options.pkceVerifierCookieName, pkceSession.verifier, {
httpOnly: true,
});
this.createVerifierCookie(res, pkceSession.verifier);
res.redirect(pkceSession.getHostedUISignupUrl());
} catch (err) {
next(err);
Expand Down Expand Up @@ -400,10 +407,7 @@ export class ExpressAuth {
}
const isSignUp = searchParams.get("isSignUp") === "true";
const tokenData = await (await this.core).getToken(code, verifier);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "lax",
});
this.createAuthCookie(res, tokenData.auth_token);
res.clearCookie(this.options.pkceVerifierCookieName);

req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
Expand Down Expand Up @@ -434,10 +438,7 @@ export class ExpressAuth {
const tokenData = await (
await this.core
).signinWithEmailPassword(email, password);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "strict",
});
this.createAuthCookie(res, tokenData.auth_token);
req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
req.tokenData = tokenData;
next();
Expand All @@ -457,19 +458,9 @@ export class ExpressAuth {
const result = await (
await this.core
).signupWithEmailPassword(email, password, verifyUrl);
res.cookie(this.options.pkceVerifierCookieName, result.verifier, {
httpOnly: true,
sameSite: "strict",
});
this.createVerifierCookie(res, result.verifier);
if (result.status === "complete") {
res.cookie(
this.options.authCookieName,
result.tokenData.auth_token,
{
httpOnly: true,
sameSite: "strict",
}
);
this.createAuthCookie(res, result.tokenData.auth_token);
req.session = new ExpressAuthSession(
this.client,
result.tokenData.auth_token
Expand Down Expand Up @@ -501,10 +492,7 @@ export class ExpressAuth {
const tokenData = await (
await this.core
).verifyEmailPasswordSignup(verificationToken, verifier);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "strict",
});
this.createAuthCookie(res, tokenData.auth_token);
res.clearCookie(this.options.pkceVerifierCookieName);

req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
Expand All @@ -526,10 +514,7 @@ export class ExpressAuth {
const { verifier } = await (
await this.core
).sendPasswordResetEmail(email, passwordResetUrl);
res.cookie(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
});
this.createVerifierCookie(res, verifier);
res.status(204);
next();
} catch (err) {
Expand All @@ -555,10 +540,7 @@ export class ExpressAuth {
const tokenData = await (
await this.core
).resetPasswordWithResetToken(resetToken, verifier, password);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "strict",
});
this.createAuthCookie(res, tokenData.auth_token);
res.clearCookie(this.options.pkceVerifierCookieName);
req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
req.tokenData = tokenData;
Expand Down Expand Up @@ -592,10 +574,7 @@ export class ExpressAuth {
const { verifier } = await (
await this.core
).resendVerificationEmailForEmail(email, verifyUrl);
res.cookie(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
});
this.createVerifierCookie(res, verifier);
} else {
throw new InvalidDataError(
"verification_token or email missing from request body"
Expand Down Expand Up @@ -633,10 +612,7 @@ export class ExpressAuth {
throw new PKCEError("no pkce verifier cookie found");
}
const tokenData = await (await this.core).getToken(code, verifier);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "strict",
});
this.createAuthCookie(res, tokenData.auth_token);
res.clearCookie(this.options.pkceVerifierCookieName);

req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
Expand Down Expand Up @@ -666,10 +642,7 @@ export class ExpressAuth {
const { verifier } = await (
await this.core
).signupWithMagicLink(email, callbackUrl, failureUrl);
res.cookie(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
});
this.createVerifierCookie(res, verifier);
next();
} catch (err) {
next(err);
Expand All @@ -694,10 +667,7 @@ export class ExpressAuth {
const { verifier } = await (
await this.core
).signinWithMagicLink(email, callbackUrl, failureUrl);
res.cookie(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
});
this.createVerifierCookie(res, verifier);
next();
} catch (err) {
next(err);
Expand All @@ -724,10 +694,7 @@ export class ExpressAuth {
const tokenData = await (
await this.core
).verifyWebAuthnSignup(verificationToken, verifier);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "strict",
});
this.createAuthCookie(res, tokenData.auth_token);
res.clearCookie(this.options.pkceVerifierCookieName);

req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
Expand Down Expand Up @@ -764,10 +731,7 @@ export class ExpressAuth {
const tokenData = await (
await this.core
).signinWithWebAuthn(email, assertion);
res.cookie(this.options.authCookieName, tokenData.auth_token, {
httpOnly: true,
sameSite: "strict",
});
this.createAuthCookie(res, tokenData.auth_token);
req.session = new ExpressAuthSession(this.client, tokenData.auth_token);
req.tokenData = tokenData;
next();
Expand Down Expand Up @@ -803,16 +767,10 @@ export class ExpressAuth {
await this.core
).signupWithWebAuthn(email, credentials, verify_url, user_handle);
const verifier = result.verifier;
res.cookie(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
});
this.createVerifierCookie(res, verifier);

if (result.status === "complete") {
res.cookie(this.options.authCookieName, result.tokenData.auth_token, {
httpOnly: true,
sameSite: "strict",
});
this.createAuthCookie(res, result.tokenData.auth_token);
req.session = new ExpressAuthSession(
this.client,
result.tokenData.auth_token
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
"react": "^18.2.0"
},
"dependencies": {
"@edgedb/auth-core": "0.2.0"
"@edgedb/auth-core": "0.2.1"
}
}
6 changes: 3 additions & 3 deletions packages/auth-nextjs/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class NextAppAuth extends NextAuth {
const tokenData = await (
await this.core
).signinWithEmailPassword(email, password);
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
return tokenData;
},
emailPasswordSignUp: async (
Expand All @@ -71,7 +71,7 @@ export class NextAppAuth extends NextAuth {
);
this.setVerifierCookie(result.verifier);
if (result.status === "complete") {
this.setSessionCookie(result.tokenData.auth_token);
this.setAuthCookie(result.tokenData.auth_token);
return result.tokenData;
}
return null;
Expand Down Expand Up @@ -113,7 +113,7 @@ export class NextAppAuth extends NextAuth {
const tokenData = await (
await this.core
).resetPasswordWithResetToken(resetToken, verifier, password);
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
cookies().delete(this.options.pkceVerifierCookieName);
return tokenData;
},
Expand Down
25 changes: 14 additions & 11 deletions packages/auth-nextjs/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,20 @@ export abstract class NextAuth extends NextAuthHelpers {
path: "/",
sameSite: "strict",
secure: this.isSecure,
expires: Date.now() + 1000 * 60 * 60 * 24 * 7, // In 7 days
});
}

setSessionCookie(token: string) {
setAuthCookie(token: string) {
const expirationDate = Auth.getTokenExpiration(token);
cookies().set({
name: this.options.authCookieName,
value: token,
httpOnly: true,
sameSite: "strict",
path: "/",
secure: this.isSecure,
expires: expirationDate ?? undefined,
});
}

Expand Down Expand Up @@ -226,7 +229,7 @@ export abstract class NextAuth extends NextAuthHelpers {
req
);
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
cookies().delete(this.options.pkceVerifierCookieName);

return onOAuthCallback(
Expand Down Expand Up @@ -283,7 +286,7 @@ export abstract class NextAuth extends NextAuthHelpers {
req
);
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
cookies().delete(this.options.pkceVerifierCookieName);

return onEmailVerify({ error: null, tokenData }, req);
Expand Down Expand Up @@ -352,7 +355,7 @@ export abstract class NextAuth extends NextAuthHelpers {
req
);
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
cookies().delete(this.options.pkceVerifierCookieName);

return onEmailVerify({ error: null, tokenData }, req);
Expand Down Expand Up @@ -408,7 +411,7 @@ export abstract class NextAuth extends NextAuthHelpers {
req
);
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
cookies().delete(this.options.pkceVerifierCookieName);

return onMagicLinkCallback(
Expand Down Expand Up @@ -484,7 +487,7 @@ export abstract class NextAuth extends NextAuthHelpers {
req
);
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
cookies().delete(this.options.pkceVerifierCookieName);

return onBuiltinUICallback(
Expand Down Expand Up @@ -555,7 +558,7 @@ export abstract class NextAuth extends NextAuthHelpers {
? _wrapResponse(onEmailPasswordSignIn({ error }, req), isAction)
: Response.json(_wrapError(error));
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
return _wrapResponse(
onEmailPasswordSignIn?.({ error: null, tokenData }, req),
isAction
Expand Down Expand Up @@ -593,7 +596,7 @@ export abstract class NextAuth extends NextAuthHelpers {
}
this.setVerifierCookie(result.verifier);
if (result.status === "complete") {
this.setSessionCookie(result.tokenData.auth_token);
this.setAuthCookie(result.tokenData.auth_token);
return _wrapResponse(
onEmailPasswordSignUp?.(
{
Expand Down Expand Up @@ -669,7 +672,7 @@ export abstract class NextAuth extends NextAuthHelpers {
? _wrapResponse(onEmailPasswordReset({ error }, req), isAction)
: Response.json(_wrapError(error));
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
cookies().delete(this.options.pkceVerifierCookieName);
return _wrapResponse(
onEmailPasswordReset?.({ error: null, tokenData }, req),
Expand Down Expand Up @@ -740,7 +743,7 @@ export abstract class NextAuth extends NextAuthHelpers {

this.setVerifierCookie(result.verifier);
if (result.status === "complete") {
this.setSessionCookie(result.tokenData.auth_token);
this.setAuthCookie(result.tokenData.auth_token);
return _wrapResponse(
onWebAuthnSignUp(
{
Expand Down Expand Up @@ -775,7 +778,7 @@ export abstract class NextAuth extends NextAuthHelpers {
const error = err instanceof Error ? err : new Error(String(err));
return _wrapResponse(onWebAuthnSignIn({ error }, req), false);
}
this.setSessionCookie(tokenData.auth_token);
this.setAuthCookie(tokenData.auth_token);
return _wrapResponse(
onWebAuthnSignIn({ error: null, tokenData }, req),
false
Expand Down
Loading

0 comments on commit 385949c

Please sign in to comment.