remix-auth-oauth2
Version:
A strategy to use and implement OAuth2 framework for authentication with federated services like Google, Facebook, GitHub, etc.
105 lines • 3.62 kB
JavaScript
import { Cookie, SetCookie } from "@mjackson/headers";
/**
* This class is used to store the state and code verifier for the OAuth2 flow.
*
* If the user is redirected to the authorization endpoint, we need to store the
* state and code verifier in a cookie so we can check that the state matches
* when the user is redirected back to the application.
*
* The problem is that the user can open multiple tabs, and we need to keep
* track of the state and code verifier for each tab. This class helps us do
* that.
*
* It's a simple class that stores the state in a Set and the code verifier in a
* Map. The state is used as the key to the code verifier, so we can easily
* retrieve it when needed. We also have a method to convert the store to a
* string, so we can store it in a cookie.
*
* The class also has a static method to create a new instance from a Request
* object, this is useful when we need to get the store from the cookie.
*/
export class StateStore {
states = new Set();
codeVerifiers = new Map();
state;
codeVerifier;
constructor(params = new URLSearchParams()) {
for (let [state, verifier] of params) {
if (state === "state")
continue;
this.states.add(state);
this.codeVerifiers.set(state, verifier);
}
}
/**
* Append a new state and code verifier to the store
*/
set(state, verifier) {
this.state = state;
this.codeVerifier = verifier;
this.states.add(state);
if (verifier)
this.codeVerifiers.set(state, verifier);
}
/**
* Check if the store has the given state
*/
has(state) {
if (state)
return this.states.has(state);
return this.states.size > 0;
}
/**
* Get the code verifier for the given state
*/
get(state) {
return this.codeVerifiers.get(state);
}
/**
* Convert the store to a string
*
* This is useful when we need to store the store in a cookie
*/
toString() {
if (!this.state)
return "";
if (!this.codeVerifier)
return "";
let params = new URLSearchParams();
params.set("state", this.state);
params.set(this.state, this.codeVerifier);
return params.toString();
}
toSetCookie(cookieName = "oauth2", options = {}) {
let id = crypto.randomUUID();
return new SetCookie({
value: this.toString(),
httpOnly: true, // Prevents JavaScript from accessing the cookie
maxAge: 60 * 5, // 5 minutes
path: "/", // Allow the cookie to be sent to any path
sameSite: "Lax", // Prevents it from being sent in cross-site requests
...options,
name: `${cookieName}:${id}`,
});
}
/**
* Create a new instance from a Request object by getting the store from a
* cookie with the given name.
*/
static fromRequest(request, cookieName = "oauth2") {
let cookie = new Cookie(request.headers.get("cookie") ?? "");
let params = new URLSearchParams();
for (let name of cookie.names) {
if (name.startsWith(cookieName)) {
let cookieInstance = cookie.get(name);
if (!cookieInstance)
continue;
for (let [key, value] of new URLSearchParams(cookieInstance)) {
params.append(key, value);
}
}
}
return new StateStore(params);
}
}
//# sourceMappingURL=store.js.map