Tokens are tentatively working correctly, read more

They also seem to be refreshing correctly too, when tokenFetch() is used.
This commit is contained in:
2021-03-10 18:54:20 +11:00
parent 44553836c7
commit b8e4b33421
6 changed files with 143 additions and 128 deletions
+4 -1
View File
@@ -5,8 +5,11 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/app';
import TokenProvider from './components/utilities/token-provider';
ReactDOM.render(
<App />,
<TokenProvider>
<App />
</TokenProvider>,
document.querySelector('#root')
);
+18 -21
View File
@@ -1,18 +1,14 @@
import React, { useState, useRef } from 'react';
import React, { useContext, useRef } from 'react';
import { Redirect } from 'react-router-dom';
import { setToken, getToken } from '../../utilities/token-client';
import { TokenContext } from '../utilities/token-provider';
const LogIn = props => {
//if logged in
const [tok, setTok] = useState(null);
//context
const authTokens = useContext(TokenContext);
getToken()
.then(token => setTok(token))
.catch(e => console.error(e))
;
if (tok) {
//misplaced?
if (authTokens.accessToken) {
return <Redirect to='/' />;
}
@@ -27,13 +23,16 @@ const LogIn = props => {
async evt => {
//on submit
evt.preventDefault();
const [result, redirect] = await handleSubmit(emailRef.current.value, passwordRef.current.value);
if (result) {
alert(result);
const [err, newTokens] = await handleSubmit(emailRef.current.value, passwordRef.current.value);
if (err) {
alert(err);
}
//redirect
if (redirect) {
//save auth tokens and redirect
if (newTokens) {
authTokens.setAccessToken(newTokens.accessToken);
authTokens.setRefreshToken(newTokens.refreshToken);
props.history.push('/');
}
}
@@ -71,18 +70,16 @@ const handleSubmit = async (email, password) => {
})
});
//handle errors
if (!result.ok) {
const err = `${result.status}: ${await result.text()}`;
console.error(err);
return [err, false];
}
//save the auth tokens
const authTokens = await result.json();
await setToken(authTokens.accessToken, authTokens.refreshToken);
return [null, true];
//return the new auth tokens
const newTokens = await result.json();
return [null, newTokens];
};
export default LogIn;
+6 -10
View File
@@ -1,22 +1,18 @@
import React, { useState, useRef } from 'react';
import React, { useContext, useRef } from 'react';
import { Redirect } from 'react-router-dom';
import { getToken } from '../../utilities/token-client';
import { TokenContext } from '../utilities/token-provider';
//utilities
const validateEmail = require('../../../common/utilities/validate-email.js');
const validateUsername = require('../../../common/utilities/validate-username.js');
const SignUp = props => {
//if logged in
const [tok, setTok] = useState(null)
//context
const authTokens = useContext(TokenContext);
getToken()
.then(token => setTok(token))
.catch(e => console.error(e))
;
if (tok) {
//misplaced?
if (authTokens.accessToken) {
return <Redirect to='/' />;
}
+27 -34
View File
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
import { getToken, getRefreshToken, clearToken } from '../../utilities/token-client';
import { TokenContext } from '../utilities/token-provider';
const Visitor = () => {
return (
@@ -14,51 +14,44 @@ const Visitor = () => {
};
const Member = () => {
const authTokens = useContext(TokenContext);
return (
<div>
<Link to='/account'>Account</Link>
<em> - </em>
<Link to='/' onClick={logout}>Log out</Link>
{ /* Logout button logs you out of the server too */ }
<Link to='/' onClick={async () => {
const result = await authTokens.tokenFetch(`${process.env.AUTH_URI}/logout`, { //NOTE: this gets overwritten as a bugfix
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
token: authTokens.refreshToken
})
});
//any problems?
if (!result.ok) {
console.error(await result.text());
} else {
authTokens.setAccessToken('');
authTokens.setRefreshToken('');
}
}}>Log out</Link>
</div>
);
};
const logout = async () => {
console.log('loging out')
const token = getToken();
//send to the auth server
const result = await fetch(`${process.env.AUTH_URI}/logout`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
token: getRefreshToken()
})
});
if (result.ok) {
await clearToken();
} else {
console.error(await result.text());
}
};
const Header = () => {
const [tok, setTok] = useState(null);
getToken()
.then(token => setTok(token))
.catch(e => console.error(e))
;
const authTokens = useContext(TokenContext);
return (
<header>
<h1><Link to='/'>MERN Template</Link></h1>
{ tok ? <Member /> : <Visitor /> }
{ authTokens.accessToken ? <Member /> : <Visitor /> }
</header>
);
};
@@ -0,0 +1,88 @@
import React, { useState, useEffect, createContext } from 'react';
import decode from 'jwt-decode';
export const TokenContext = createContext();
const TokenProvider = props => {
const [accessToken, setAccessToken] = useState('');
const [refreshToken, setRefreshToken] = useState('');
//make the access and refresh tokens persist between reloads
useEffect(() => {
setAccessToken(localStorage.getItem("accessToken") || '');
setRefreshToken(localStorage.getItem("refreshToken") || '');
}, [])
React.useEffect(() => {
localStorage.setItem("accessToken", accessToken);
localStorage.setItem("refreshToken", refreshToken);
}, [accessToken, refreshToken]);
//wrap the default fetch function
const tokenFetch = async (url, options) => {
//use this?
let bearer = accessToken;
//if expired (10 minutes, normally)
const expired = new Date(decode(accessToken).exp * 1000) < Date.now();
if (expired) {
//ping the auth server for a new token
const response = await fetch(`${process.env.AUTH_URI}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
token: refreshToken
})
});
//any errors, throw them
if (!response.ok) {
throw `${response.status}: ${await response.text()}`;
}
//save the new auth stuff (setting bearer as well)
const newAuth = await response.json();
setAccess(newAuth.accessToken);
setRefresh(newAuth.refreshToken);
bearer = newAuth.accessToken;
//BUGFIX: logging out correctly requires the new refresh token
if (url == `${process.env.AUTH_URI}/logout`) {
console.log(`logging out with refresh token: ${newAuth.refreshToken}`)
return fetch(`${process.env.AUTH_URI}/logout`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Authorization': `Bearer ${bearer}`
},
body: JSON.stringify({
token: newAuth.refreshToken
})
});
}
}
//finally, delegate to fetch
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${bearer}`
}
});
};
return (
<TokenContext.Provider value={{ accessToken, refreshToken, setAccessToken, setRefreshToken, tokenFetch }}>
{props.children}
</TokenContext.Provider>
)
};
export default TokenProvider;
-62
View File
@@ -1,62 +0,0 @@
import decode from 'jwt-decode';
import Cookies from 'universal-cookie';
export async function setToken(access, refresh) {
const cookies = new Cookies();
cookies.set('access', access, { path: '/' });
cookies.set('refresh', refresh, { path: '/' });
};
export async function getToken() {
const cookies = new Cookies();
try {
const access = cookies.get('access');
const refresh = cookies.get('refresh');
if (!access || !refresh) {
return null;
}
//if expired, refresh
if (new Date(decode(access).exp * 1000) < Date.now()) {
const result = await fetch(`${process.env.AUTH_URI}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
token: refresh
})
});
const authTokens = await result.json();
await setToken(authTokens.accessToken, authTokens.refreshToken);
return authTokens.accessToken;
} else {
return access;
}
}
catch (e) {
console.error(e);
return null;
}
};
export async function getRefreshToken() {
const cookies = new Cookies();
const refresh = cookies.get('refresh');
return refresh || null;
};
export async function clearToken() {
const cookies = new Cookies();
cookies.remove('access');
cookies.remove('refresh');
}