認可認証の実装　フロントエンド:nextjs 　バックエンド:SpringSecurityのjwt認証 


# 概要
webアプリケーションの認可認証を実装します。
フロントエンドの側の実装がメインになります
バックエンドのSpringBootは仕上がっている体で進めます。postmanでのE2Eテストしかしません

以下フレームワークを使って実装します。

バックエンド：SpringBoot　restapi
フロントエンド：next.js react typescript axios
認可認証:SpringSecurity

# 開発環境
開発環境
OS:windows10

バックエンド側：
IDE:IntelliJ Community
spring-boot-starter-parent 2.75
java　：　11

フロントエンド：
IDE:VScode

├── @types/node@18.11.15
├── @types/react-dom@18.0.9
├── @types/react@18.0.26
├── axios@1.2.1
├── eslint-config-next@13.0.6
├── eslint@8.29.0
├── next-auth@4.18.7
├── next@13.0.6
├── react-bootstrap@2.7.0
├── react-dom@18.2.0
├── react@18.2.0
├── styled-components@5.3.6
├── styled-jsx@5.1.1
└── typescript@4.9.4

# 図とか
## 認証の動き
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/54a5e2e9-ffda-24de-6eab-cb34d40d1ea9.png)


## 認可の動き
バックエンドの動きははかなり端折ってます。実際はややこしいです。
Controllerレイヤーに入る前にアクセストークンのバリデーション処理が入るよって理解で十分だと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/ada5ed78-be84-2ee7-7003-4453c3f41756.png)


## フロントエンドファイル構成（仮）
検証用ページ作って動きを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/91aec1fa-6103-9d24-cb7b-b4c63b21cd43.png)



# 実装
## バックエンド側
実装は済んでいるものとして進めます。

### Postmanでの認証
ログインしてから、レスポンスで返されるaccesstokenを使って認可が必要なapiを叩いている様子です。

![login_postman.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/930c800a-dd99-9afb-61eb-f2c290020e68.gif)


## フロントエンドの側の実装

### ソースコード
``` customer.d.ts
/** 
* @remarks ユースケース上、ログイン機能の実装するために使います。顧客の詳細情報(電話番号、住所など)はCustomerDetail側の管轄になります。
*
* @author RYA234
* @link {signin.ts}
*/
export  type Customer = {
	append(arg0: string, inputEmail: string): unknown
	/** Eメールアドレスです。ログインするときに必要な情報です　入力した情報とMySql側で照合します。*/
	email:string 
	/** パスワードです。 ログインするときに必要な情報です。SpringSecurity側でエンコードされます。*/
	password:string 
}


```


``` customerService.ts

import axios from 'axios';
import { URLSearchParams } from 'url';
import { Customer } from '../types/customer';



export const signIn = ({email,password}: Customer) =>{
	return axios.post(`http://127.0.0.1:5000/api/auth/signin`,
	{ email: email, password: password },
	{ headers: { 
		'Request-Method' : 'POST',
		'Content-Type': 'application/json',
		'Access-Control-Allow-Origin': '*',
		'Access-Control-Allow-Headers': '*',
		'Access-Control-Allow-Credentials': 'true'
	} } //COR回避
		
	);
};

export const logout =()=>{
	return axios.post(`http://127.0.0.1:5000/api/auth/logout`);
}


//認可が必要なapi 本来だったら別カテゴリーに移す
export const getCartItems =(jwtAccessKey : string)=>{
	return axios.get(`http://127.0.0.1:5000/api/cart/all`,
	{ headers : {
			'Authorization': `Bearer ${jwtAccessKey}`
		}}
	);
}

```

``` authcheck.tsx
import { MouseEventHandler, useRef, useState } from "react";
import * as customerService from "../../service/customerService";
import { Customer } from "../../types/customer";
import { JwtResponse } from "../../types/jwtResponse.t";
import { Button, CarouselItem, Form } from 'react-bootstrap';
import { Category } from "../../types/category";


/**
 * @remarks 検証用ページ <br/
 *			以下の２点の実装方法を確認するためのページです。<br/>
 *	  	  1. nextjsをフロントとしてSpringSecurityの認証するための実装方法確認<br/>
 *		  2. 認証成功後、認可が必要なapiを使えるか実装方法確認y<br/>	
 * @returns 
 * 
 * 
 */
export default function Authcheck(){

	// 開発効率上げるため　この時点で登録情報は入力済みとする
	// 登録情報はMysqlに保存され　passwordはバックエンド側でbcrypt方式で暗号化され保存される。 
	const [inputEmail,setInputEmail] = useState<string>('aaaba@gmail.com');
	const [inputPassword,setInputPassword] = useState<string>('test');
	const [jwtAccessToken, setJwtAccessToken] = useState<string | null>();
	const [cartItemMessage,setCartItemMessage] = useState<string>();
	const [presentCartItems, setPresentCartItems] = useState<cartItemType[]>([]);

	// 本当は型定義ファイルに移すべきだが、今回はここに書く
	type cartItemType = {
		id:number,
		customerId:number,
		productId:number,
		quantity:number,
	}
	
	// 認証ログイン処理
	const submitHandler=(event : React.FocusEvent<HTMLFormElement>) => {
		event.preventDefault();
		const requestBody : Customer  ={
			email: inputEmail,
			password: inputPassword,			
			append: function (arg0: string, inputEmail: string): unknown {
				throw new Error("Function not implemented.");
			}
		} ;
		customerService
		.signIn(requestBody)
		.then((response) => {
			localStorage.setItem('accessToken', response.data.accessToken)
			setJwtAccessToken(response.data.accessToken);})
		.catch((error) => { 
			localStorage.setItem('accessToken', ' error');
			setJwtAccessToken(localStorage.getItem('accessToken'));})
		;
	}

	// 認可　顧客の買い物かごの情報を取得する。
	const getCategoriesHandler = (onClick? : React.MouseEvent<HTMLButtonElement> | undefined) => {
		customerService
		.getCartItems(jwtAccessToken as string)
		.then((cartItems) => {
				cartItems.data.map((cartItem : cartItemType) => { 
				setPresentCartItems(presentCartItems => [...presentCartItems,{
					id:cartItem.id,
					customerId:cartItem.customerId,
					productId:cartItem.productId,
					quantity:cartItem.quantity
				}]);
				setCartItemMessage('成功');
				}
				)
		})
		.catch((error: any) => {
			setPresentCartItems([]); // 初期化
			setCartItemMessage('失敗');
		})	
	}
	return(
		<>
			<h2>auth jwt 認証 ログイン</h2>
			フロント側:NextJs<br />
			バックエンド側:Springboot SpringSecurity<br />
			動き
			<br />
			URI http://localhost:5000/api/auth/signinにPOST Methodを送ります。<br />
			<br />
			リクエストボディにはjson形式で ユーザーが入力したemailとpasswordが含まれています。<br />
			正しいemailとpasswordが入力された場合、localstorageがJWT-Tokenが収納されます。<br />
			誤ったemailとpasswordが入力された場合、localstorageに「error」が収納されます。<br />

			local:storageの値「UseStateに渡している値」:<br />
			{jwtAccessToken}
			<Form onSubmit = {submitHandler} >
			<Form.Group className="mb-3" controlId="formBasicEmail">
				<Form.Label>Email address</Form.Label><br />
				<Form.Control 
					type="email" 
					placeholder="Enter email"  
					value  = {inputEmail as string}
					onChange={(event) => setInputEmail(event.target.value)}
					/><br />
			</Form.Group>

			<Form.Group className="mb-3" controlId="formBasicPassword">
				<Form.Label>Password</Form.Label><br />
				<Form.Control 
					type="password" 
					placeholder="Password" 
					value ={inputPassword as string}
					onChange={(event) => setInputPassword(event.target.value)}
					/><br /><br />
			</Form.Group>

			<Button variant="primary" type="submit">
				Submit
			</Button>
			</Form>
		<div>
			<h2>auth jwt 認可</h2>
			認証が必要なAPIを叩けるか実際に確認する。<br />
				ユースケース的には、ECショップにおいてユーザーが自分の買い物カゴの情報を取得します。<br />
				<Button onClick={getCategoriesHandler}>買い物カゴの情報を取得する。</Button>
				<br />
				id, 商品Id、数量
				 {presentCartItems.map((cartItem:cartItemType) => {
					return(
						// eslint-disable-next-line react/jsx-key
						<div>
						{cartItem.id}  {cartItem.productId} {cartItem.quantity}
						</div>
					)
				 }
				)}
				<br/><br/>
				api実行できたか<br/>
				{cartItemMessage}
		</div>
	</>
	)
}

```

### 認証(ログイン)ができるか検証する
localstorageにaccessTokenが入っているので　期待した動きができてますね
![login_OK.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/1714aeac-9f6f-d2f5-ed09-e88acccc4473.gif)


### 認証後　認可が必要なapiが実行できるか検証する。
情報が取得されてるので、実行されてますね。
![認可完了_OK.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/4b58778c-8690-8ced-dae3-c1fa3e7d69eb.gif)



### 認証せず、認可が必要なapiが実行できないことを検証する。
以下 gifアニメは accessTokenを消して、認可が必要なAPIを叩いている様子です。

![認可完了_NGpatter1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/f293791c-b343-b3cb-fffb-a08295733d7e.gif)




```
		.catch((error: any) => {
			setPresentCartItems([]); // 初期化
			setCartItemMessage('失敗');
		})	
```

らへんの処理をしているので期待した動きをしています。


あとはログアウト処理できれば機能的に十分そう