Flo*_*n_L 5 javascript authentication cookies reactjs next.js
我目前正在使用Nextjs和使用Expressjs的 api实现身份验证流程。
我希望将 a 存储JWT token
为身份验证令牌in memory
,我可以使用存储在HTTPOnly cookie
.
对于我的实现,我在这里参考了不错的 OSS 项目。
我的问题是,当我inMemoryToken
在登录期间存储身份验证令牌时,该值仅存储在可用的客户端但仍然可用的服务器端,反之亦然。
另一个例子是当我断开连接时:
inMemoryToken
等于服务器端的东西logout()
称为前端和inMemoryToken = null
getServerSideProps()
在服务器inMemoryToken
上调用,但在服务器上仍然等于以前的值,因此我的用户仍然显示为已连接。这是Nextjs
代码
//auth.js
import { Component } from 'react';
import Router from 'next/router';
import { serialize } from 'cookie';
import { logout as fetchLogout, refreshToken } from '../services/api';
let inMemoryToken;
export const login = ({ accessToken, accessTokenExpiry }, redirect) => {
inMemoryToken = {
token: accessToken,
expiry: accessTokenExpiry,
};
if (redirect) {
Router.push('/');
}
};
export const logout = async () => {
inMemoryToken = null;
await fetchLogout();
window.localStorage.setItem('logout', Date.now());
Router.push('/');
};
const subMinutes = (dt, minutes) => {
return new Date(dt.getTime() - minutes * 60000);
};
export const withAuth = (WrappedComponent) => {
return class extends Component {
static displayName = `withAuth(${Component.name})`;
state = {
accessToken: this.props.accessToken,
};
async componentDidMount() {
this.interval = setInterval(async () => {
inMemoryToken = null;
const token = await auth();
inMemoryToken = token;
this.setState({ accessToken: token });
}, 60000);
window.addEventListener('storage', this.syncLogout);
}
componentWillUnmount() {
clearInterval(this.interval);
window.removeEventListener('storage', this.syncLogout);
window.localStorage.removeItem('logout');
}
syncLogout(event) {
if (event.key === 'logout') {
Router.push('/');
}
}
render() {
return (
<WrappedComponent
{...this.props}
accessToken={this.state.accessToken}
/>
);
}
};
};
export const auth = async (ctx) => {
console.log('auth ', inMemoryToken);
if (!inMemoryToken) {
inMemoryToken = null;
const headers =
ctx && ctx.req
? {
Cookie: ctx.req.headers.cookie ?? null,
}
: {};
await refreshToken(headers)
.then((res) => {
if (res.status === 200) {
const {
access_token,
access_token_expiry,
refresh_token,
refresh_token_expiry,
} = res.data;
if (ctx && ctx.req) {
ctx.res.setHeader(
'Set-Cookie',
serialize('refresh_token', refresh_token, {
path: '/',
expires: new Date(refresh_token_expiry),
httpOnly: true,
secure: false,
}),
);
}
login({
accessToken: access_token,
accessTokenExpiry: access_token_expiry,
});
} else {
let error = new Error(res.statusText);
error.response = res;
throw error;
}
})
.catch((e) => {
console.log(e);
if (ctx && ctx.req) {
ctx.res.writeHead(302, { Location: '/auth' });
ctx.res.end();
} else {
Router.push('/auth');
}
});
}
const accessToken = inMemoryToken;
if (!accessToken) {
if (!ctx) {
Router.push('/auth');
}
}
return accessToken;
};
Run Code Online (Sandbox Code Playgroud)
//page index.js
import Head from 'next/head';
import { Layout } from '../components/Layout';
import { Navigation } from '../components/Navigation';
import { withAuth, auth } from '../libs/auth';
const Home = ({ accessToken }) => (
<Layout>
<Head>
<title>Home</title>
</Head>
<Navigation />
<div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
</Layout>
);
export const getServerSideProps = async (ctx) => {
const accessToken = await auth(ctx);
return {
props: { accessToken: accessToken ?? null },
};
};
export default withAuth(Home);
Run Code Online (Sandbox Code Playgroud)
express js代码的一部分:
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
....
const refreshToken = uuidv4();
const refreshTokenExpiry = new Date(new Date().getTime() + 10 * 60 * 1000);
res.cookie('refresh_token', refreshToken, {
maxAge: 10 * 60 * 1000,
httpOnly: true,
secure: false,
});
res.json({
access_token: accessToken,
access_token_expiry: accessTokenExpiry,
refresh_token: refreshToken,
user,
});
});
app.post('/api/refresh-token', (req, res) => {
const refreshToken = req.cookies['refresh_token'];
.....
const newRefreshToken = uuidv4();
const newRefreshTokenExpiry = new Date(
new Date().getTime() + 10 * 60 * 1000,
);
res.cookie('refresh_token', newRefreshToken, {
maxAge: 10 * 60 * 1000,
httpOnly: true,
secure: false,
});
res.json({
access_token: accessToken,
access_token_expiry: accessTokenExpiry,
refresh_token: newRefreshToken,
refresh_token_expiry: newRefreshTokenExpiry,
});
});
app.post('/api/logout', (_, res) => {
res.clearCookie('refresh_token');
res.sendStatus(200);
});
Run Code Online (Sandbox Code Playgroud)
我的理解是,即使let inMemoryToken
声明一次,它的两个单独的实例将在运行时可用,一个客户端和一个服务器端,并且修改不会影响另一个。我对吗?
在这种情况下,如何解决这个问题,因为 auth 方法可以在服务器上调用,也可以在客户端调用?
我创建了一个示例,展示如何使用会话在内存中为单个用户跨请求存储信息。如果您只对代码感兴趣,可以查看底部的代码和框。
有两件事需要记住:
但是,您可以使用存储在客户端 cookie 中的通用 ID,并将数据存储在内存中,并通过请求的会话来访问它。
当你有一个请求传入时,你可以检查请求头中是否存在cookie,如果存在则尝试从内存中加载会话,如果不存在或无法加载,则创建一个新会话。
| Incoming Request
| |--> Check the cookie header for your session key
| |--> If cookie exists load cookie
| |--> Else create session + use 'set-cookie' header to tell the client it's session key
| |--> Do stuff with the data stored in the session
Run Code Online (Sandbox Code Playgroud)
为了能够做到这一点,我们需要有某种方法来存储会话以及与其相关的数据。你说你只想将数据存储在内存中。
const memoryStore = new Map();
Run Code Online (Sandbox Code Playgroud)
好吧,现在我们有了内存存储,但是我们如何让它在请求之间持久存在呢?让我们将其存储为全局对象。
const MEMORY_STORE = Symbol.for('__MEMORY_STORE');
const getMemoryStore = () => {
if (!global[MEMORY_STORE]) {
global[MEMORY_STORE] = new Map();
}
return global[MEMORY_STORE];
};
Run Code Online (Sandbox Code Playgroud)
完美,现在我们可以调用getMemoryStore
来访问持久数据。现在我们要创建一个处理程序,尝试从请求加载会话,否则创建一个新会话。
const SESSION_KEY = '__my_session_id';
const loadSession = (req, res) => {
const memory = getMemoryStore();
const cookies = parseCookies(req.headers.cookie);
const cookieSession = cookies[SESSION_KEY];
// check to make sure that cookieSession is defined and that it exists in the memory store
if (cookieSession && memory.has(cookieSession)) {
const session = memory.get(cookieSession);
req.session = session;
// do something with the session
} else {
// okay the session doesn't exists so we need to create one, create the unique session id
const sessionId = uuid();
const session = { id: sessionId };
memory.set(sessionId, session);
// set the set-cookie header on the response with the session ID
res.setHeader('set-cookie', `${SESSION_KEY}=${sessionId}`);
req.session = session;
}
};
Run Code Online (Sandbox Code Playgroud)
现在我们可以在服务器端的任何地方调用它,它将加载或创建会话。例如,您可以从getServerSideProps
export const getServerSideProps = ({ req, res }) => {
loadSession(req, res);
// our session exists on req.session !!
return { props: { ... } };
};
Run Code Online (Sandbox Code Playgroud)
我制作了一个有工作示例的codesandbox:https://codesandbox.io/s/distracted-water-biicc ?file=/utils/app.js
归档时间: |
|
查看次数: |
5374 次 |
最近记录: |