fix: set cookie's `SameSite` and `Secure`

fix/auth-cookie
Arik Chakma 10 months ago
parent 7ba48523da
commit 92d562baed
  1. 11
      Caddyfile
  2. 8
      src/components/AuthenticationFlow/EmailLoginForm.tsx
  3. 8
      src/components/AuthenticationFlow/GitHubButton.tsx
  4. 8
      src/components/AuthenticationFlow/GoogleButton.tsx
  5. 8
      src/components/AuthenticationFlow/LinkedInButton.tsx
  6. 10
      src/components/AuthenticationFlow/ResetPasswordForm.tsx
  7. 8
      src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
  8. 7
      src/components/Navigation/navigation.ts
  9. 4
      src/components/UpdateProfile/UploadProfilePicture.tsx
  10. 24
      src/lib/http.ts
  11. 17
      src/lib/jwt.ts

@ -0,0 +1,11 @@
api.roadmap.localhost {
reverse_proxy localhost:8080
}
roadmap.localhost {
reverse_proxy localhost:3000
}
draw.roadmap.localhost {
reverse_proxy localhost:4321
}

@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { httpPost } from '../../lib/http'; import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
type EmailLoginFormProps = { type EmailLoginFormProps = {
isDisabled?: boolean; isDisabled?: boolean;
@ -34,11 +34,7 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
// Log the user in and reload the page // Log the user in and reload the page
if (response?.token) { if (response?.token) {
Cookies.set(TOKEN_COOKIE_NAME, response.token, { setAuthToken(response.token);
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
window.location.reload(); window.location.reload();
return; return;

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx'; import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import { httpGet } from '../../lib/http'; import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx'; import { Spinner } from '../ReactIcons/Spinner.tsx';
@ -70,11 +70,7 @@ export function GitHubButton(props: GitHubButtonProps) {
localStorage.removeItem(GITHUB_REDIRECT_AT); localStorage.removeItem(GITHUB_REDIRECT_AT);
localStorage.removeItem(GITHUB_LAST_PAGE); localStorage.removeItem(GITHUB_LAST_PAGE);
Cookies.set(TOKEN_COOKIE_NAME, response.token, { setAuthToken(response.token);
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
window.location.href = redirectUrl; window.location.href = redirectUrl;
}) })
.catch((err) => { .catch((err) => {

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import { httpGet } from '../../lib/http'; import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx'; import { Spinner } from '../ReactIcons/Spinner.tsx';
import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx'; import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx';
@ -69,11 +69,7 @@ export function GoogleButton(props: GoogleButtonProps) {
localStorage.removeItem(GOOGLE_REDIRECT_AT); localStorage.removeItem(GOOGLE_REDIRECT_AT);
localStorage.removeItem(GOOGLE_LAST_PAGE); localStorage.removeItem(GOOGLE_LAST_PAGE);
Cookies.set(TOKEN_COOKIE_NAME, response.token, { setAuthToken(response.token);
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
window.location.href = redirectUrl; window.location.href = redirectUrl;
}) })
.catch((err) => { .catch((err) => {

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import { httpGet } from '../../lib/http'; import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx'; import { Spinner } from '../ReactIcons/Spinner.tsx';
import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx'; import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx';
@ -69,11 +69,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
localStorage.removeItem(LINKEDIN_REDIRECT_AT); localStorage.removeItem(LINKEDIN_REDIRECT_AT);
localStorage.removeItem(LINKEDIN_LAST_PAGE); localStorage.removeItem(LINKEDIN_LAST_PAGE);
Cookies.set(TOKEN_COOKIE_NAME, response.token, { setAuthToken(response.token);
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
window.location.href = redirectUrl; window.location.href = redirectUrl;
}) })
.catch((err) => { .catch((err) => {

@ -1,7 +1,7 @@
import { type FormEvent, useEffect, useState } from 'react'; import { type FormEvent, useEffect, useState } from 'react';
import { httpPost } from '../../lib/http'; import { httpPost } from '../../lib/http';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
export function ResetPasswordForm() { export function ResetPasswordForm() {
const [code, setCode] = useState(''); const [code, setCode] = useState('');
@ -37,7 +37,7 @@ export function ResetPasswordForm() {
newPassword: password, newPassword: password,
confirmPassword: passwordConfirm, confirmPassword: passwordConfirm,
code, code,
} },
); );
if (error?.message) { if (error?.message) {
@ -53,11 +53,7 @@ export function ResetPasswordForm() {
} }
const token = response.token; const token = response.token;
Cookies.set(TOKEN_COOKIE_NAME, token, { setAuthToken(response.token);
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
window.location.href = '/'; window.location.href = '/';
}; };

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { httpPost } from '../../lib/http'; import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import { Spinner } from '../ReactIcons/Spinner'; import { Spinner } from '../ReactIcons/Spinner';
import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2'; import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
@ -26,11 +26,7 @@ export function TriggerVerifyAccount() {
return; return;
} }
Cookies.set(TOKEN_COOKIE_NAME, response.token, { setAuthToken(response.token);
path: '/',
expires: 30,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
window.location.href = '/'; window.location.href = '/';
}) })
.catch((err) => { .catch((err) => {

@ -1,11 +1,8 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, removeAuthToken } from '../../lib/jwt';
export function logout() { export function logout() {
Cookies.remove(TOKEN_COOKIE_NAME, { removeAuthToken();
path: '/',
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
// Reloading will automatically redirect the user if required // Reloading will automatically redirect the user if required
window.location.reload(); window.location.reload();

@ -1,6 +1,6 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { type ChangeEvent, type FormEvent, useEffect, useRef, useState } from 'react'; import { type ChangeEvent, type FormEvent, useEffect, useRef, useState } from 'react';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; import { TOKEN_COOKIE_NAME, removeAuthToken } from '../../lib/jwt';
interface PreviewFile extends File { interface PreviewFile extends File {
preview: string; preview: string;
@ -128,7 +128,7 @@ export default function UploadProfilePicture(props: UploadProfilePictureProps) {
// Logout user if token is invalid // Logout user if token is invalid
if (data.status === 401) { if (data.status === 401) {
Cookies.remove(TOKEN_COOKIE_NAME); removeAuthToken();
window.location.reload(); window.location.reload();
} }
}; };

@ -1,6 +1,6 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import fp from '@fingerprintjs/fingerprintjs'; import fp from '@fingerprintjs/fingerprintjs';
import { TOKEN_COOKIE_NAME } from './jwt'; import { TOKEN_COOKIE_NAME, removeAuthToken } from './jwt';
type HttpOptionsType = RequestInit | { headers: Record<string, any> }; type HttpOptionsType = RequestInit | { headers: Record<string, any> };
@ -30,10 +30,10 @@ type ApiReturn<ResponseType, ErrorType> = {
*/ */
export async function httpCall< export async function httpCall<
ResponseType = AppResponse, ResponseType = AppResponse,
ErrorType = AppError ErrorType = AppError,
>( >(
url: string, url: string,
options?: HttpOptionsType options?: HttpOptionsType,
): Promise<ApiReturn<ResponseType, ErrorType>> { ): Promise<ApiReturn<ResponseType, ErrorType>> {
try { try {
const fingerprintPromise = await fp.load({ monitoring: false }); const fingerprintPromise = await fp.load({ monitoring: false });
@ -65,7 +65,7 @@ export async function httpCall<
// Logout user if token is invalid // Logout user if token is invalid
if (data.status === 401) { if (data.status === 401) {
Cookies.remove(TOKEN_COOKIE_NAME); removeAuthToken();
window.location.reload(); window.location.reload();
return { response: undefined, error: data as ErrorType }; return { response: undefined, error: data as ErrorType };
} }
@ -92,11 +92,11 @@ export async function httpCall<
export async function httpPost< export async function httpPost<
ResponseType = AppResponse, ResponseType = AppResponse,
ErrorType = AppError ErrorType = AppError,
>( >(
url: string, url: string,
body: Record<string, any>, body: Record<string, any>,
options?: HttpOptionsType options?: HttpOptionsType,
): Promise<ApiReturn<ResponseType, ErrorType>> { ): Promise<ApiReturn<ResponseType, ErrorType>> {
return httpCall<ResponseType, ErrorType>(url, { return httpCall<ResponseType, ErrorType>(url, {
...options, ...options,
@ -108,7 +108,7 @@ export async function httpPost<
export async function httpGet<ResponseType = AppResponse, ErrorType = AppError>( export async function httpGet<ResponseType = AppResponse, ErrorType = AppError>(
url: string, url: string,
queryParams?: Record<string, any>, queryParams?: Record<string, any>,
options?: HttpOptionsType options?: HttpOptionsType,
): Promise<ApiReturn<ResponseType, ErrorType>> { ): Promise<ApiReturn<ResponseType, ErrorType>> {
const searchParams = new URLSearchParams(queryParams).toString(); const searchParams = new URLSearchParams(queryParams).toString();
const queryUrl = searchParams ? `${url}?${searchParams}` : url; const queryUrl = searchParams ? `${url}?${searchParams}` : url;
@ -122,11 +122,11 @@ export async function httpGet<ResponseType = AppResponse, ErrorType = AppError>(
export async function httpPatch< export async function httpPatch<
ResponseType = AppResponse, ResponseType = AppResponse,
ErrorType = AppError ErrorType = AppError,
>( >(
url: string, url: string,
body: Record<string, any>, body: Record<string, any>,
options?: HttpOptionsType options?: HttpOptionsType,
): Promise<ApiReturn<ResponseType, ErrorType>> { ): Promise<ApiReturn<ResponseType, ErrorType>> {
return httpCall<ResponseType, ErrorType>(url, { return httpCall<ResponseType, ErrorType>(url, {
...options, ...options,
@ -138,7 +138,7 @@ export async function httpPatch<
export async function httpPut<ResponseType = AppResponse, ErrorType = AppError>( export async function httpPut<ResponseType = AppResponse, ErrorType = AppError>(
url: string, url: string,
body: Record<string, any>, body: Record<string, any>,
options?: HttpOptionsType options?: HttpOptionsType,
): Promise<ApiReturn<ResponseType, ErrorType>> { ): Promise<ApiReturn<ResponseType, ErrorType>> {
return httpCall<ResponseType, ErrorType>(url, { return httpCall<ResponseType, ErrorType>(url, {
...options, ...options,
@ -149,10 +149,10 @@ export async function httpPut<ResponseType = AppResponse, ErrorType = AppError>(
export async function httpDelete< export async function httpDelete<
ResponseType = AppResponse, ResponseType = AppResponse,
ErrorType = AppError ErrorType = AppError,
>( >(
url: string, url: string,
options?: HttpOptionsType options?: HttpOptionsType,
): Promise<ApiReturn<ResponseType, ErrorType>> { ): Promise<ApiReturn<ResponseType, ErrorType>> {
return httpCall<ResponseType, ErrorType>(url, { return httpCall<ResponseType, ErrorType>(url, {
...options, ...options,

@ -31,3 +31,20 @@ export function getUser() {
return decodeToken(token); return decodeToken(token);
} }
export function setAuthToken(token: string) {
Cookies.set(TOKEN_COOKIE_NAME, token, {
path: '/',
expires: 30,
sameSite: 'lax',
secure: true,
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
}
export function removeAuthToken() {
Cookies.remove(TOKEN_COOKIE_NAME, {
path: '/',
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
});
}

Loading…
Cancel
Save