Created
December 21, 2021 21:55
-
-
Save chanan/d1601c63df36ac476e84fbc2ca96b2e9 to your computer and use it in GitHub Desktop.
Cognito authentication method for Remix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { redirect, createCookie } from "remix"; | |
const sessionSecret = process.env.SESSION_SECRET; | |
const cognitoDomain = process.env.COGNITO_DOMAIN; | |
const clientId = process.env.CLIENT_ID; | |
if (!sessionSecret) { | |
throw new Error("SESSION_SECRET must be set"); | |
} | |
if (!cognitoDomain) { | |
throw new Error("COGNITO_DOMAIN must be set"); | |
} | |
if (!clientId) { | |
throw new Error("CLIENT_ID must be set"); | |
} | |
const cookieSettings = { | |
maxAge: 60 * 60 * 30, | |
secure: process.env.NODE_ENV === "production", | |
secrets: [sessionSecret], | |
httpOnly: true | |
} | |
const cookieAccessToken = createCookie("cognitoAccessToken", cookieSettings); | |
const cookieIdToken = createCookie("cognitoIdToken", cookieSettings); | |
const cookieRefreshToken = createCookie("cognitoRefreshToken", cookieSettings); | |
export async function authenticate(request) { | |
const url = new URL(request.url); | |
const redirectUri = url.origin + url.pathname; | |
const code = url.searchParams.get("code"); | |
const redirectTo = encodeURIComponent(url.searchParams.get("redirectTo") || "/"); | |
const headers = new Headers(); | |
let user = null; | |
if (code) { | |
//If the url has a code, we redirected the user to the cognito and they were authenticated | |
const tokenResponse = await getToken(code, redirectUri); | |
if (tokenResponse.status === 200) { | |
const json = await tokenResponse.json(); | |
const { access_token, id_token, refresh_token } = json; | |
user = await getUser(access_token); | |
headers.append("Set-cookie", await cookieAccessToken.serialize({ | |
access_token | |
})); | |
headers.append("Set-cookie", await cookieIdToken.serialize({ | |
id_token | |
})); | |
headers.append("Set-cookie", await cookieRefreshToken.serialize({ | |
refresh_token | |
})); | |
} | |
} | |
//The url does not have a code, so this is the first time we are hitting the login page | |
//First try to get a user from an access token saved as a cookie | |
if (!user) { | |
user = await hasValidAccessToken(request); | |
if (!user) { | |
//Then try to refresh the access token from a refresh token saved as a cookie | |
const { accessToken, idToken, refreshToken } = await refreshAccessToken(request, redirectUri); | |
if (accessToken) { | |
user = await getUser(accessToken); | |
if (user) { | |
headers.append("Set-cookie", await cookieAccessToken.serialize({ | |
access_token: accessToken | |
})); | |
headers.append("Set-cookie", await cookieIdToken.serialize({ | |
id_token: idToken | |
})); | |
headers.append("Set-cookie", await cookieRefreshToken.serialize({ | |
refresh_token: refreshToken | |
})); | |
} | |
} | |
} | |
if (!user) { | |
//if we still have no user then send them to the cognito login page | |
const uri = `https://${cognitoDomain}/login?client_id=${clientId}&response_type=code&scope=email+openid&redirect_uri=${redirectUri}&state=${redirectTo}`; | |
return redirect(uri); | |
} | |
} | |
if (user) { | |
//TODO Persist the user in the session | |
console.log("This should be persisted in session: ", user); | |
const state = url.searchParams.get("state"); | |
const finalRedirectTo = decodeURIComponent(state || redirectTo); | |
console.log('finalRedirectTo :>> ', finalRedirectTo); | |
return redirect(finalRedirectTo, { headers }); | |
} | |
//All failed, return to login | |
return redirect(`/login?redirect=${redirectTo}`); | |
} | |
//Make the call to cognito to get the token | |
async function getToken(code, redirectUri) { | |
const uri = `https://${cognitoDomain}/oauth2/token`; | |
const body = { | |
grant_type: "authorization_code", | |
client_id: clientId, | |
redirect_uri: redirectUri, | |
code | |
} | |
return response = await fetch(uri, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded" | |
}, | |
body: new URLSearchParams(body) | |
}); | |
} | |
//Get the user info. If this call succeeds, the user is authenticated | |
async function getUser(access_token) { | |
const uri = `https://${cognitoDomain}/oauth2/userInfo`; | |
const response = await fetch(uri, { | |
method: "GET", | |
headers: { | |
"Authorization": `Bearer ${access_token}` | |
}, | |
}); | |
if (response.status === 200) { | |
return await response.json(); | |
} else { | |
return null; | |
} | |
} | |
//Does the user have a valid access token? If so, return the user info | |
async function hasValidAccessToken(request) { | |
const cookieHeaders = request.headers.get("Cookie"); | |
if (cookieHeaders) { | |
const cookieAccessTokenValue = await (cookieAccessToken.parse(cookieHeaders) || {}); | |
if (cookieAccessTokenValue.access_token) { | |
return await getUser(cookieAccessTokenValue.access_token); | |
} | |
} | |
return null; | |
} | |
async function refreshAccessToken(request, redirectUri) { | |
const ret = { | |
accessToken: undefined, | |
idToken: undefined, | |
refreshToken: undefined | |
} | |
const cookieHeaders = request.headers.get("Cookie"); | |
if (cookieHeaders) { | |
const cookieRefreshTokenValue = await (cookieRefreshToken.parse(cookieHeaders) || {}); | |
if (cookieRefreshTokenValue.refresh_token) { | |
const uri = `https://${cognitoDomain}/oauth2/token`; | |
const body = { | |
grant_type: "refresh_token", | |
client_id: clientId, | |
redirect_uri: redirectUri, | |
refresh_token: cookieRefreshTokenValue.refresh_token | |
} | |
const response = await fetch(uri, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded" | |
}, | |
body: new URLSearchParams(body) | |
}); | |
if (response.status === 200) { | |
const json = await response.json(); | |
const { access_token, id_token, refresh_token } = json; | |
ret.accessToken = access_token; | |
ret.idToken = id_token; | |
ret.refreshToken = refresh_token; | |
} | |
} | |
} | |
return ret; | |
} |
This code is pretty old, I am not sure its good. Some things to keep in mind. First Remix now has a good auth library that you might be better off using: https://github.com/sergiodxa/remix-auth
Second even if you do use this gist, you can imporve it by checking the JWT token locally rather than hitting /userInfo
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Shouldn't the code exchange request include the client secret?