반응형
지금까지 서버에서 인증 토큰을 받아 처리하는 작업을 진행했었습니다. 그럼 이제부터는 서버에서 참조하는 인증 코드를 클라이언트에서 처리하는 작업을 하겠습니다.
우선 ArticlePage.js에서는 로그인 한 사용자가 있으면 그 로그인 사용자의 인증 토큰을 가지고 와서 서버에 Axios로 요청을 할때 Header에 인증 토큰을 실어서 같이 보내는 작업을 합니다.
ArticlePage.js
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
import NotFoundPage from './NotFoundPage';
import CommentsList from '../components/CommentsList';
import AddCommentForm from '../components/AddCommentForm';
import useUser from '../hooks/useUser';
import articles from './article-content';
const ArticlePage = () => {
const [articleInfo, setArticleInfo] = useState({ upvotes: 0, comments: [] });
const { articleId } = useParams();
const { user, isLoading } = useUser();
useEffect(() => {
const loadArticleInfo = async () => {
const token = user && await user.getIdToken(); // if there is a logged-in user, get an user auth token
const headers = token ? { authtoken: token } : {}; // there is a token, add to header
const response = await axios.get(`/api/articles/${articleId}`, { headers }); // add header info
const newArticleInfo = response.data;
setArticleInfo(newArticleInfo);
}
loadArticleInfo();
}, []);
const article = articles.find(article => article.name === articleId);
const addUpvote = async () => {
const token = user && await user.getIdToken();
const headers = token ? { authtoken: token } : {};
const response = await axios.put(`/api/articles/${articleId}/upvote`, null, { headers });
const updatedArticle = response.data;
setArticleInfo(updatedArticle);
}
if (!article) {
return <NotFoundPage />
}
return (
<>
<h1>{article.title}</h1>
<div className="upvotes-section">
{user
? <button onClick={addUpvote}>Upvote</button>
: <button>Log in to upvote</button>}
<p>This article has {articleInfo.upvotes} upvote(s)</p>
</div>
{article.content.map((paragraph, i) => (
<p key={i}>{paragraph}</p>
))}
{user
? <AddCommentForm
articleName={articleId}
onArticleUpdated={updatedArticle => setArticleInfo(updatedArticle)} />
: <button>Log in to add a comment</button>}
<CommentsList comments={articleInfo.comments} />
</>
);
}
export default ArticlePage;
서버 페이지도 다시한번 정리해 보겠습니다. 인증 정보를 사용할 Credentials.json파일의 경로와 사용자 아이디를 확인 하는 부분을 약간 수정했습니다. 사용자가 로그인 하지 않았는데도 페이지를 요청할경우를 대비해서 코드를 수정합니다.
server.js
import fs from 'fs';
import admin from 'firebase-admin';
import express from 'express';
import { db, connectToDb } from './db.js';
const credentials = JSON.parse(
fs.readFileSync('./credentials.json')
);
admin.initializeApp({
credential: admin.credential.cert(credentials),
});
const app = express();
app.use(express.json());
app.use(async (req, res, next) => {
const { authtoken } = req.headers;
if (authtoken) {
try {
req.user = await admin.auth().verifyIdToken(authtoken);
} catch (e) {
return res.sendStatus(400);
}
}
req.user = req.user || {}; // add default value for req.user
next();
});
app.get('/api/articles/:name', async (req, res) => {
const { name } = req.params;
const { uid } = req.user;
const article = await db.collection('articles').findOne({ name });
if (article) {
const upvoteIds = article.upvoteIds || [];
article.canUpvote = uid && !upvoteIds.includes(uid);
res.json(article);
} else {
res.sendStatus(404);
}
});
app.use((req, res, next) => {
if (req.user) {
next();
} else {
res.sendStatus(401);
}
});
app.put('/api/articles/:name/upvote', async (req, res) => {
const { name } = req.params;
const { uid } = req.user;
const article = await db.collection('articles').findOne({ name });
if (article) {
const upvoteIds = article.upvoteIds || [];
const canUpvote = uid && !upvoteIds.includes(uid);
if (canUpvote) {
await db.collection('articles').updateOne({ name }, {
$inc: { upvotes: 1 },
$push: { upvoteIds: uid },
});
}
const updatedArticle = await db.collection('articles').findOne({ name });
res.json(updatedArticle);
} else {
res.send('That article doesn\'t exist');
}
});
app.post('/api/articles/:name/comments', async (req, res) => {
const { name } = req.params;
const { text } = req.body;
const { email } = req.user;
await db.collection('articles').updateOne({ name }, {
$push: { comments: { postedBy: email, text } },
});
const article = await db.collection('articles').findOne({ name });
if (article) {
res.json(article);
} else {
res.send('That article doesn\'t exist!');
}
});
connectToDb(() => {
console.log('Successfully connected to database!');
app.listen(8000, () => {
console.log('Server is listening on port 8000');
});
})
그리고 댓글을 작성할수 있는 컴포넌트에도 인증 토큰을 헤더에 함께 실어 보내는 작업을 해줍니다.
AddCommentForm.js
import { useState } from 'react';
import axios from 'axios';
import useUser from '../hooks/useUser';
const AddCommentForm = ({ articleName, onArticleUpdated }) => {
const [name, setName] = useState('');
const [commentText, setCommentText] = useState('');
const { user } = useUser();
const addComment = async () => {
const token = user && await user.getIdToken();
const headers = token ? { authtoken: token } : {};
const response = await axios.post(`/api/articles/${articleName}/comments`, {
postedBy: name,
text: commentText,
}, {
headers,
});
const updatedArticle = response.data;
onArticleUpdated(updatedArticle);
setName('');
setCommentText('');
}
return (
<div id="add-comment-form">
<h3>Add a Comment</h3>
<label>
Name:
<input
value={name}
onChange={e => setName(e.target.value)}
type="text" />
</label>
<label>
Comment:
<textarea
value={commentText}
onChange={e => setCommentText(e.target.value)}
rows="4"
cols="50" />
</label>
<button onClick={addComment}>Add Comment</button>
</div>
)
}
export default AddCommentForm;
서버와 클라이언트를 다시 시작하고 결과를 확인합니다.
반응형
'PROGRAMING > FULL STACK' 카테고리의 다른 글
[Host] Preparing an app for release (1) | 2024.03.28 |
---|---|
[Auth] Making interface adjustments for authenticated users (1) | 2024.03.28 |
[Auth] Protecting the upvote and comment endpoints (0) | 2024.03.26 |
[Auth] Protecting endpoints using auth-tokens (0) | 2024.03.26 |
[Auth] Adding Firebase Auth to Node.js (0) | 2024.03.26 |