devise-token-authを用いたログイン・ログアウト機能の作成②
ログアウト機能の作成
前回、『devise-token-authを用いたログイン・ログアウト機能の作成①』 という記事を作成しました。
今回解説する記事はその記事の続きで、ここでは、ログアウト機能の実装方法を解説します。
ログアウト機能もこちらの記事を参考に実装しました。
実装方針としては、まず、ログイン後に Cookieへ保存した access-token, client, uid をパラメータとした HTTPヘッダ を作成します。
次に、そのHTTPヘッダ を用いて、ユーザー情報を取得し、ユーザーが持つトークン(User.last.tokens などで取得できる token, client_id, created_at , expiry などの情報)を削除します。
さらに、ログアウト処理が完了したら Cookieの中に保存している access-token, client, uid も削除します。
今回は、上記のような方針で実装してみました。実装結果は下記の通りです。
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { styled, useTheme } from '@mui/material/styles';
import { useNavigate } from 'react-router-dom';
import {
IconButton,
Drawer,
List,
ListItem,
ListItemIcon,
ListItemText,
} from '@mui/material';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import LockOpenIcon from '@mui/icons-material/LockOpen';
import ExitToAppIcon from '@mui/icons-material/ExitToApp';
import { userSignOut } from '../../apis/userSignOut';
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
justifyContent: 'flex-start',
}));
const Header = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const user = useSelector(state => state.user)
const dispatch = useDispatch()
const navigate = useNavigate();
// サイドバーの開閉
const toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen);
};
const handleSignInClick = () => {
toggleSidebar()
navigate('/users/sign_in');
}
// ログアウト
const handleSignOutClick = () => {
// Cookieを取得
const cookies = document.cookie.split(';');
const cookieData = cookies.reduce((data, cookie) => {
const [key, value] = cookie.trim().split('=');
data[key] = value;
return data;
}, {});
const access_token = cookieData['access-token'] || null;
const client = cookieData['client'] || null;
const uid = cookieData['uid'] || null;
const headers = {
'access-token': access_token,
'client': client,
'uid': uid
};
userSignOut(headers, dispatch);
toggleSidebar();
};
const theme = useTheme();
const handleDrawerClose = () => {
toggleSidebar()
};
return (
<>
<Drawer anchor="right" open={isSidebarOpen} onClose={toggleSidebar}>
<div className='sidebar'>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
</IconButton>
</DrawerHeader>
<List>
{!user?.accessToken &&
<ListItem button onClick={handleSignInClick}>
<ListItemIcon>
<LockOpenIcon />
</ListItemIcon>
<ListItemText primary="ログイン" />
</ListItem>
}
{user?.accessToken &&
<ListItem button onClick={handleSignOutClick}>
<ListItemIcon>
<ExitToAppIcon />
</ListItemIcon>
<ListItemText primary="ログアウト" />
</ListItem>
}
</List>
</div>
</Drawer>
</>
);
};
export default Header;
上記の Header component では、ログアウトボタンを作成し、クリックすると handleSignOutClick という関数が実行されます。この関数は、Cookie に保存してある access-token, client, uid を取得し、それらをパラメータにもつHTTPヘッダを作成しています。
そして、作成したHTTPヘッダを用いて userSignOut というログアウト処理をrails API として呼び出す関数を実行しています。
const DEFAULT_API_LOCALHOST = 'http://localhost:3010/api/v1'
export const logoutIndex = `${DEFAULT_API_LOCALHOST}/auth/sign_out`
import axios from 'axios';
import { logoutIndex } from '../urls/index'
import { deleteUserData } from '../reducks/reducers/user';
// ログアウト
export const userSignOut = async(headers, dispatch) => {
await axios.delete(logoutIndex, { headers: headers })
.then(response => {
if (navigator.cookieEnabled)
{
document.cookie = 'access-token=;max-age=0;';
document.cookie = 'client=;max-age=0;';
document.cookie = 'uid=;max-age=0;';
}
dispatch(deleteUserData(response.data))
window.location.reload();
alert('ログアウト成功しました。')
}).catch(error => {
console.log(error);
});
};
上記の userSignOut 関数では、react側から送信されたHTTPヘッダを用いて、HTTPリクエストのDELETEを実行し、ログアウト処理の rails APIをたたいています。
また、rails API のログアウト処理が成功したら 、Cookie に保存している access-token, client, uid も削除しています。
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
sessions: 'api/v1/auth/sessions'
}
end
end
end
上記の routes.rb では、ログイン処理同様、ログアウト処理もルーティングされています。
class Api::V1::Auth::SessionsController < DeviseTokenAuth::SessionsController
-- 省略 ---
def destroy
# 認証情報を含むヘッダーからトークン情報を取得
client_id = request.headers['client']
uid = request.headers['uid']
access_token = request.headers['access-token']
# トークン情報を使用してユーザーを特定し、トークンを無効化する
user = User.find_by_uid(uid)
user.tokens.delete(client_id) if user
if user&.save
render json: { message: 'ログアウトしました。' }
else
render json: { errors: ['ログアウトに失敗しました。'] }, status: :unprocessable_entity
end
end
end
上記の sessions_controller.rb では、destroyアクションを定義し、ログアウト処理を定義しています。
ここでは、raect側から送られてきたHTTPヘッダの中にある client, uid を取得し、取得した uid からその uid を持つユーザー情報を取得しています。
そして、その特定したユーザーが持つトークン情報(User.last.tokens などで取得できる token, client_id, created_at , expiry などの情報)を削除しています。
以上が、devise-token-auth を用いた ログアウト処理の実装方法の説明となります。
Discussion