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 ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import App from './components/app';
|
import App from './components/app';
|
||||||
|
import TokenProvider from './components/utilities/token-provider';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<App />,
|
<TokenProvider>
|
||||||
|
<App />
|
||||||
|
</TokenProvider>,
|
||||||
document.querySelector('#root')
|
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 { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { setToken, getToken } from '../../utilities/token-client';
|
import { TokenContext } from '../utilities/token-provider';
|
||||||
|
|
||||||
const LogIn = props => {
|
const LogIn = props => {
|
||||||
//if logged in
|
//context
|
||||||
const [tok, setTok] = useState(null);
|
const authTokens = useContext(TokenContext);
|
||||||
|
|
||||||
getToken()
|
//misplaced?
|
||||||
.then(token => setTok(token))
|
if (authTokens.accessToken) {
|
||||||
.catch(e => console.error(e))
|
|
||||||
;
|
|
||||||
|
|
||||||
if (tok) {
|
|
||||||
return <Redirect to='/' />;
|
return <Redirect to='/' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +23,16 @@ const LogIn = props => {
|
|||||||
async evt => {
|
async evt => {
|
||||||
//on submit
|
//on submit
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const [result, redirect] = await handleSubmit(emailRef.current.value, passwordRef.current.value);
|
const [err, newTokens] = await handleSubmit(emailRef.current.value, passwordRef.current.value);
|
||||||
if (result) {
|
if (err) {
|
||||||
alert(result);
|
alert(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
//redirect
|
//save auth tokens and redirect
|
||||||
if (redirect) {
|
if (newTokens) {
|
||||||
|
authTokens.setAccessToken(newTokens.accessToken);
|
||||||
|
authTokens.setRefreshToken(newTokens.refreshToken);
|
||||||
|
|
||||||
props.history.push('/');
|
props.history.push('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,18 +70,16 @@ const handleSubmit = async (email, password) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//handle errors
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
const err = `${result.status}: ${await result.text()}`;
|
const err = `${result.status}: ${await result.text()}`;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return [err, false];
|
return [err, false];
|
||||||
}
|
}
|
||||||
|
|
||||||
//save the auth tokens
|
//return the new auth tokens
|
||||||
const authTokens = await result.json();
|
const newTokens = await result.json();
|
||||||
|
return [null, newTokens];
|
||||||
await setToken(authTokens.accessToken, authTokens.refreshToken);
|
|
||||||
|
|
||||||
return [null, true];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LogIn;
|
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 { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { getToken } from '../../utilities/token-client';
|
import { TokenContext } from '../utilities/token-provider';
|
||||||
|
|
||||||
//utilities
|
//utilities
|
||||||
const validateEmail = require('../../../common/utilities/validate-email.js');
|
const validateEmail = require('../../../common/utilities/validate-email.js');
|
||||||
const validateUsername = require('../../../common/utilities/validate-username.js');
|
const validateUsername = require('../../../common/utilities/validate-username.js');
|
||||||
|
|
||||||
const SignUp = props => {
|
const SignUp = props => {
|
||||||
//if logged in
|
//context
|
||||||
const [tok, setTok] = useState(null)
|
const authTokens = useContext(TokenContext);
|
||||||
|
|
||||||
getToken()
|
//misplaced?
|
||||||
.then(token => setTok(token))
|
if (authTokens.accessToken) {
|
||||||
.catch(e => console.error(e))
|
|
||||||
;
|
|
||||||
|
|
||||||
if (tok) {
|
|
||||||
return <Redirect to='/' />;
|
return <Redirect to='/' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { getToken, getRefreshToken, clearToken } from '../../utilities/token-client';
|
import { TokenContext } from '../utilities/token-provider';
|
||||||
|
|
||||||
const Visitor = () => {
|
const Visitor = () => {
|
||||||
return (
|
return (
|
||||||
@@ -14,51 +14,44 @@ const Visitor = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Member = () => {
|
const Member = () => {
|
||||||
|
const authTokens = useContext(TokenContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Link to='/account'>Account</Link>
|
<Link to='/account'>Account</Link>
|
||||||
<em> - </em>
|
<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>
|
</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 Header = () => {
|
||||||
const [tok, setTok] = useState(null);
|
const authTokens = useContext(TokenContext);
|
||||||
|
|
||||||
getToken()
|
|
||||||
.then(token => setTok(token))
|
|
||||||
.catch(e => console.error(e))
|
|
||||||
;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<h1><Link to='/'>MERN Template</Link></h1>
|
<h1><Link to='/'>MERN Template</Link></h1>
|
||||||
{ tok ? <Member /> : <Visitor /> }
|
{ authTokens.accessToken ? <Member /> : <Visitor /> }
|
||||||
</header>
|
</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