owned this note
owned this note
Published
Linked with GitHub
# Nuxt3-[sidebase/nuxt-auth](https://sidebase.io/nuxt-auth/v0.5/getting-started)
## 安裝
<!-- 測試網址:https://b83a-220-129-5-211.ngrok-free.app
測試帳號:six5c1a@example.com
測試密碼:password -->
```bash
# -V5
npm i --save-exact @sidebase/nuxt-auth@0.5.0
# next-auth
npm i --save-exact next-auth@4.21.1
```
## Laravel Passport
Authorization Code Grant Flow (授權碼模式)
### 環境設置
#### callback
需至Laravel Passport設置
`http://localhost/api/auth/callback/laravelpassport`
(開發環境的預設值,根據環境進行修改,可以新增多個 URI,以逗號分隔)
#### nuxt.config.ts
:::spoiler nuxt.config.ts
```javascript=
export default ({
modules: [
'@sidebase/nuxt-auth'
],
auth: {
globalAppMiddleware: false,
},
runtimeConfig:{
passport: {
// secret: process.env.PASSPORT_SECRET,
baseUrl: process.env.PASSPORT_BASE_URL,
clientId: process.env.PASSPORT_CLIENT_ID,
clientSecret: process.env.PASSPORT_CLIENT_SECRET,
}
}
})
```
:::
#### .env
:::spoiler .env
```bash=
AUTH_ORIGIN=http://localhost
# 命令視窗 使用 openssl rand -base64 32 產生隨機亂數
PASSPORT_SECRET=將產生的亂數貼在這
PASSPORT_BASE_URL=enter-your-base-url
PASSPORT_CLIENT_ID=enter-your-client-id-here
PASSPORT_CLIENT_SECRET=enter-your-client-secret-here
```
:::
#### server
:::spoiler server/api/auth/[...].js
```javascript=
import { NuxtAuthHandler } from '#auth'
const { passport } = useRuntimeConfig();
export default NuxtAuthHandler({
secret: passport.secret,
//需要取得accessToken,需添加這個callback
callbacks: {
jwt: async ({token, account}) => {
if (account) {
token.access_token = account.access_token
}
return token
},
// 如果需要存入在cookie則添加這段
// session({ session, token }) {
// session.access_token = token.access_token
// return session
// },
},
providers: [
{
// 僅用於回呼URL
id: "laravelpassport",
// 用於登入按鈕
name: "Passport",
// 連接類型
type: "oauth",
version: "2.0",
authorization: {
// 授權網址
url: passport.baseUrl+'/oauth/authorize',
// 所需的權限範圍
params: {
scope: '*',
}
},
token: {
// 獲取和更新令牌的位址,需依狀況跟改路徑
url: passport.baseUrl+'/oauth/token',
},
// 客戶端的id
clientId: passport.clientId,
// 客戶端的密鑰
clientSecret: passport.clientSecret,
userinfo: {
// 獲取登入者的訊息位址,需依狀況跟改路徑
url: passport.baseUrl+'/api/me',
},
profile: (profile) => {
return {
// id為必要屬性不可以省略
id: profile.id,
};
},
idToken: false,
}
]
})
```
:::
:::spoiler server/api/token.get.js
```javascript=
import { getToken } from '#auth'
export default eventHandler(async (event) => {
const token = await getToken({ event})
return token?.access_token || 'no token present'
})
```
:::
#### composables
:::spoiler composables/useLogin.js
```javascript=
export const useLogin = () => {
const { status, data, signIn, signOut } = useAuth()
const loggedIn = computed(()=> status.value === 'authenticated')
const currentAuthData = computed(()=> data.value)
const handleSigIn = async(providers, callback) =>{
if(callback){
await signIn(providers,{callbackUrl:callback});
} else{
await signIn(providers);
}
}
const handleSigOut = async() =>{
await signOut();
}
//取得完整accessToken
const headers = useRequestHeaders(['cookie'])
const { data: token } = useFetch('/api/token', { headers })
return {
data,
token,
loggedIn,
handleSigIn,
handleSigOut
}
}
```
:::
#### components
:::spoiler components/Login.vue
```htmlembedded=
<template>
<div>
<!-- 實際呈現方式依需求設置 -->
<template v-if="loggedIn">
<div>
Token:
<p>
{{ token || 'no token present, are you logged in?' }}
</p>
</div>
<div>
會員資料:
<p>
{{ data }}
</p>
</div>
<button @click="handleSigOut()">登出</button>
</template>
<template v-else>
<button @click="handleSigIn('laravelpassport', '/callbackurl')">登入</button>
</template>
</div>
</template>
<script setup>
const {loggedIn, data, token, handleSigIn, handleSigOut} = useLogin()
</script>
```
:::
#### pages
:::spoiler page/index.vue
```htmlembedded=
<template>
<div>
<div>{{ token || 'no token present, are you logged in?' }}</div>
<Login />
</div>
</template>
<script setup>
definePageMeta({
auth: false
});
const headers = useRequestHeaders(['cookie'])
const { data: token } = await useFetch('/api/token')
</script>
```
:::
### 除錯歷程(前端部分已修正至上方完整程式內)
- OAUTH_CALLBACK_ERROR
- error message
:::spoiler
![image](https://hackmd.io/_uploads/r1j9XIkcT.png)
:::
- 錯誤修正
後端缺少userinfo回傳訊息,開發階段請後端至少return 一組固定 id
- OAUTH_PARSE_PROFILE_ERROR
- error message
:::spoiler
![截圖 2024-01-25 上午11.27.19](https://hackmd.io/_uploads/H148HI1cp.png)
:::
- 錯誤修正
將[...].js中 profile 的 return 添加id
- 因設計的登入流程需要取得完整的accessToken
- 修正方式
- [...].js檔添加callback-jwt
:::spoiler server/api/auth/[...].js
```javascript=
import { NuxtAuthHandler } from '#auth'
const { passport } = useRuntimeConfig();
export default NuxtAuthHandler({
//需要取得accessToken,需添加這個callback
callbacks: {
jwt: async ({token, account}) => {
if (account) {
token.access_token = account.access_token
}
return token
},
// session({ session, token }) {
// session.access_token = token.access_token
// return session
// },
},
providers: [
......
]
})
```
:::
- 新建 token.get.js檔
:::spoiler server/api/token.get.js
```javascript=
import { getToken } from '#auth'
export default eventHandler(async (event) => {
const token = await getToken({ event})
return token?.access_token || 'no token present'
})
```
:::
- 參考資料
- [nuxt-auth/issues#280](https://github.com/sidebase/nuxt-auth/issues/280)
- [@sidebase/nuxt-auth-jwt](https://sidebase.io/nuxt-auth/server-side/jwt-access)
------------------------------------------------
## GitHub登入
### 環境設置
#### nuxt.config.ts
:::spoiler nuxt.config.ts
```javascript=
export default ({
modules: [
'@sidebase/nuxt-auth'
],
auth: {
globalAppMiddleware: false,
},
runtimeConfig:{
githubProvider: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_SECRET,
}
}
})
```
:::
#### .env
:::spoiler .env
```bash=
# GitHub登入
GITHUB_CLIENT_ID=enter-your-client-id-here
GITHUB_SECRET=enter-your-client-secret-here
```
:::
#### server
:::spoiler server/api/auth/[...].js
```javascript=
import { NuxtAuthHandler } from '#auth'
import GithubProvider from 'next-auth/providers/github'
const { githubProvider } = useRuntimeConfig();
export default NuxtAuthHandler({
providers: [
// GitHub登入
GithubProvider.default({
clientId: githubProvider.clientId,
clientSecret: githubProvider.clientSecret
})
]
})
```
:::
#### composables
:::spoiler composables/useLogin.js
```javascript=
export const useLogin = () => {
const { status, data, signIn, signOut } = useAuth()
const loggedIn = computed(()=> status.value === 'authenticated')
const handleSigIn = async() =>{
await signIn();
}
const handleSigOut = async() =>{
await signOut();
}
return {
loggedIn,
data,
handleSigIn,
handleSigOut
}
}
```
:::
#### components
:::spoiler components/Login.vue
```htmlembedded=
<template>
<div>
<template v-if="loggedIn">
<button @click="handleSigOut()">登出</button>
</template>
<template v-else>
<button @click="handleSigIn('github')">登入</button>
</template>
</div>
</template>
<script setup>
const {loggedIn, data, handleSigIn, handleSigOut} = useLogin()
</script>
```
:::
#### pages
:::spoiler page/index.vue
```htmlembedded=
<template>
<div>
<Login />
</div>
</template>
<script setup>
definePageMeta({
auth: false
});
</script>
```
:::