Tokens are tentatively working correctly, read more
They also seem to be refreshing correctly too, when tokenFetch() is used.
This commit is contained in:
+4
-1
@@ -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')
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -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='/' />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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');
|
||||
}
|
||||
Reference in New Issue
Block a user