Improve OpenID login flow security

This commit is contained in:
Jesse Wierzbinski 2023-12-06 13:34:56 -10:00
parent d47a11cfc2
commit 22ebf72b6b
No known key found for this signature in database
5 changed files with 77 additions and 28 deletions

View file

@ -5,8 +5,10 @@ import type { MatchedRoute } from "bun";
import {
calculatePKCECodeChallenge,
discoveryRequest,
generateRandomCodeVerifier,
processDiscoveryResponse,
} from "oauth4webapi";
import { client } from "~database/datasource";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -63,7 +65,22 @@ export default async (
algorithm: "oidc",
}).then(res => processDiscoveryResponse(issuerUrl, res));
const codeVerifier = "tempString";
const codeVerifier = generateRandomCodeVerifier();
// Store into database
const newFlow = await client.openIdLoginFlow.create({
data: {
codeVerifier,
application: {
connect: {
client_id: clientId,
},
},
issuerId,
},
});
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
return Response.redirect(
@ -72,7 +89,7 @@ export default async (
new URLSearchParams({
client_id: issuer.client_id,
redirect_uri:
oauthRedirectUri(issuerId) + `?clientId=${clientId}`,
oauthRedirectUri(issuerId) + `?flow=${newFlow.id}`,
response_type: "code",
scope: "openid profile email",
// PKCE

View file

@ -52,8 +52,18 @@ export default async (
// Remove state query parameter from URL
currentUrl.searchParams.delete("state");
const issuerParam = matchedRoute.params.issuer;
// This is the Lysand client's client_id, not the external OAuth provider's client_id
const clientId = matchedRoute.query.clientId;
const flow = await client.openIdLoginFlow.findFirst({
where: {
id: matchedRoute.query.flow,
},
include: {
application: true,
},
});
if (!flow) {
return redirectToLogin("Invalid flow");
}
const config = getConfig();
@ -95,8 +105,8 @@ export default async (
client_secret: issuer.client_secret,
},
parameters,
oauthRedirectUri(issuerParam) + `?clientId=${clientId}`,
"tempString"
oauthRedirectUri(issuerParam) + `?flow=${flow.id}`,
flow.codeVerifier
);
const result = await processAuthorizationCodeOpenIDResponse(
@ -153,24 +163,19 @@ export default async (
return redirectToLogin("No user found with that account");
}
const application = await client.application.findFirst({
where: {
client_id: clientId,
},
});
if (!application) return redirectToLogin("Invalid client_id");
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!flow.application) return redirectToLogin("Invalid client_id");
const code = randomBytes(32).toString("hex");
await client.application.update({
where: { id: application.id },
where: { id: flow.application.id },
data: {
tokens: {
create: {
access_token: randomBytes(64).toString("base64url"),
code: code,
scope: application.scopes,
scope: flow.application.scopes,
token_type: TokenType.BEARER,
user: {
connect: {
@ -183,5 +188,8 @@ export default async (
});
// Redirect back to application
return Response.redirect(`${application.redirect_uris}?code=${code}`, 302);
return Response.redirect(
`${flow.application.redirect_uris}?code=${code}`,
302
);
};