Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion php/laravel/api/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"laravel/framework": "^9.2",
"laravel/sanctum": "^2.14.1",
"laravel/tinker": "^2.7",
"saasus-platform/saasus-sdk-php": "v1.1.0"
"saasus-platform/saasus-sdk-php": "1.2.0"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
Expand Down
14 changes: 7 additions & 7 deletions php/laravel/api/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions php/laravel/api/routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

// 一時コードからIDトークンなどの認証情報を取得するコントローラを登録
Route::get('/callback', 'AntiPatternInc\Saasus\Laravel\Controllers\CallbackApiController@index');
Route::get('/token/refresh', 'AntiPatternInc\Saasus\Laravel\Controllers\TokenRefreshApiController@index');

// SaaSus SDK標準のAuth Middlewareを利用する
Route::middleware(\AntiPatternInc\Saasus\Laravel\Middleware\Auth::class)->group(function () {
Expand Down
104 changes: 103 additions & 1 deletion php/laravel/front/src/lib/axios.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,103 @@
import Axios from 'axios'
import Axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'

const sleep = (second: number) =>
new Promise((resolve) => setTimeout(resolve, second * 1000))

type Jwt = {
[name: string]: string | number | boolean
}

const decodeJwt = (token: string): Jwt => {
const base64Url = token.split('.')[1]
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
return JSON.parse(decodeURIComponent(escape(window.atob(base64)))) as Jwt
}

const getNewIdToken = async (): Promise<string> => {
try {
const { data } = await Axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/api/token/refresh`,
{
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
withCredentials: true,
}
)
if (!data?.id_token || !data?.access_token) {
throw new Error('failed to get new credentials')
}
return data.id_token as string
} catch (err) {
console.error(err)
throw new Error('failed to get new credentials')
}
}

const onReqFulfilled = async (config: AxiosRequestConfig) => {
if (!config?.headers) {
return config
}
const authorizationHeader = config.headers.Authorization as string
const idToken = authorizationHeader?.split(' ')[1]
if (!idToken) {
return config
}

let decodedIdToken: Jwt
try {
decodedIdToken = decodeJwt(idToken)
} catch (err) {
console.error(err)
// リダイレクト処理などを入れる
return
}
const expireDate = decodedIdToken['exp'] as number
const timestamp = parseInt(Date.now().toString().slice(0, 10))
// トークンの有効期限が切れている場合は新しいトークンを取得する
if (expireDate <= timestamp) {
try {
const newIdToken = await getNewIdToken()
config.headers.Authorization = `Bearer ${newIdToken}`
window.localStorage.setItem('SaaSusIdToken', newIdToken)
// JWTを更新してすぐ使用すると、Token used before used エラーになるため。
// ref: https://github.com/dgrijalva/jwt-go/issues/383
await sleep(1)
return config
} catch (err) {
console.error(err)
// リダイレクト処理などを入れる
}
}
return config
}

const onResFulfilled = (res: AxiosResponse) => res
const onResRejected = async (error: AxiosError) => {
if (!error?.response) {
return Promise.reject(error)
}
if (error?.response.status !== 401) {
return Promise.reject(error)
}
const config = error.config
if (!config.headers) {
return Promise.reject(error)
}
try {
const newIdToken = await getNewIdToken()
config.headers.Authorization = `Bearer ${newIdToken}`
window.localStorage.setItem('SaaSusIdToken', newIdToken)
// JWTを更新してすぐ使用すると、Token used before used エラーになるため。
// ref: https://github.com/dgrijalva/jwt-go/issues/383
await sleep(1)
return config
} catch (err) {
console.error(err)
// リダイレクト処理などを入れる
}
return
}

const axios = Axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
Expand All @@ -8,4 +107,7 @@ const axios = Axios.create({
withCredentials: true,
})

axios.interceptors.request.use(onReqFulfilled)
axios.interceptors.response.use(onResFulfilled, onResRejected)

export default axios
12 changes: 10 additions & 2 deletions php/laravel/front/src/pages/callback/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import Container from '@mui/material/Container'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import axios from '@/lib/axios'
import Axios from 'axios'

const Callback = () => {
const router = useRouter()
const query = router.query
const code = query.code as string

const fetchAuthCredentials = async () => {
const res = await axios.get(`/api/callback?code=${code}`)
const res = await Axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/api/callback?code=${code}`,
{
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
withCredentials: true,
},
)
// 渡ってきたJWTをLocal Storageに保存する
const idToken = res.data.id_token as string
localStorage.setItem('SaaSusIdToken', idToken)
Expand Down