I want to use a CSRF token, and I understand that for each form submission, I need to send my CSRF token. The token is generated once when a user signs in (it's a random UID). Here's what happens during sign-in: In Angular, I sign in the user, which gives me an ID token. Then, I generate a UID for my CSRF token. I send this to my backend, which creates two HTTP-only cookies: one with the session cookie obtained by exchanging the ID token, and the other with the CSRF token, which is simply the random UID.
The idea is that for each subsequent request, I compare the UID from the form to the one stored in the cookie. If they match, it's okay. However, the problem arises because the session cookie is valid for two weeks. When the user closes their browser, they remain logged in, but the random UID is lost. However, the CSRF cookie remains. So, for each request the user makes thereafter, there won't be the same CSRF token, and the request won't be authorized.
I think I'm missing something, and I don't think I'm approaching this the right way. Can anyone help me?
import { Router } from "express";
import { getAuth } from "firebase-admin/auth";
const sessionRouter = Router();
sessionRouter.post("/", (req, res) => {
// Get the ID token passed and the CSRF token.
const idToken = req.body.idToken.toString();
const csrfToken = req.body.csrfToken.toString();
// Guard against CSRF attacks.
if (!csrfToken) {
res.status(401).send("UNAUTHORIZED REQUEST!");
return;
}
// Set session expiration to 14 days.
const expiresIn = 60 * 60 * 24 * 14 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// To only allow session cookie setting on recent sign-in, auth_time in ID token
// can be checked to ensure user was recently signed in before creating a session cookie.
getAuth()
.createSessionCookie(idToken, { expiresIn })
.then(
(sessionCookie) => {
// Set cookie policy for session cookie.
const options = {
maxAge: expiresIn,
httpOnly: true,
secure: false,
samesite: "None",
};
res.cookie("session", sessionCookie, options);
res.cookie("csrfToken", csrfToken, options);
res.end(JSON.stringify({ status: "success" }));
},
(error) => {
res.status(401).send("UNAUTHORIZED REQUEST!");
}
);
});
export default sessionRouter;
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { environment } from '../../environments/environment.development';
import { initializeApp } from 'firebase/app';
import {
getAuth,
setPersistence,
inMemoryPersistence,
signInWithEmailAndPassword,
UserCredential,
} from 'firebase/auth';
import { UuidService } from './uuid.service';
import { RequestService } from './request.service';
@Injectable({
providedIn: 'root',
})
export class FirebaseAuthService {
app = initializeApp(environment.firebaseConfig);
auth = getAuth(this.app);
constructor(
private uuidService: UuidService,
private router: Router,
private requestService: RequestService
) {
setPersistence(this.auth, inMemoryPersistence);
}
async signIn(email: string, password: string) {
try {
const userCredential: UserCredential = await signInWithEmailAndPassword(
this.auth,
email,
password
);
const csrfToken = this.uuidService.uuidValue;
const idToken = await userCredential.user.getIdToken();
this.requestService.postIdTokenToSessionLogin(idToken, csrfToken);
} catch (error) {}
}
}
import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment.development';
@Injectable({
providedIn: 'root',
})
export class RequestService {
URL = environment.url;
HEADERS = {
'Content-Type': 'application/json',
};
constructor() {}
async postIdTokenToSessionLogin(idToken: string, csrfToken: string) {
try {
const response = await fetch(`${this.URL}/sessionLogin`, {
method: 'POST',
headers: this.HEADERS,
credentials: 'include',
body: JSON.stringify({ idToken, csrfToken }),
});
const data = await response.json();
console.log('Réponse du serveur :', data);
} catch (error) {}
}
}
import { Injectable } from '@angular/core';
import { v4 as uuidv4 } from 'uuid';
@Injectable({
providedIn: 'root',
})
export class UuidService {
uuid = '';
constructor() {
this.uuid = uuidv4();
}
get uuidValue() {
return this.uuid;
}
}