本文档描述如何将 Logto 认证集成到 Xboard。
Xboard now supports Logto as an authentication provider, offering:
- OAuth 2.0 + OpenID Connect (OIDC) authentication
- Single Sign-On (SSO)
- Multi-Factor Authentication (MFA)
- Social login providers
- Enterprise SSO (SAML)
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Frontend │────────▶│ Logto │────────▶│ Xboard │
│ (Vue3) │ OIDC │ Auth Server │ Token │ Backend │
└─────────────┘ └──────────────┘ └─────────────┘
│ │
│ │
▼ ▼
User Authentication Business Data
The Logto SDK has been added to composer.json:
composer installAdd the following to your .env file:
# Logto Authentication
LOGTO_ENDPOINT=https://your-logto.app
LOGTO_APP_ID=your_app_id
LOGTO_APP_SECRET=your_app_secret
LOGTO_REDIRECT_URI=${APP_URL}/api/v1/passport/auth/logto/callback
LOGTO_POST_LOGOUT_REDIRECT_URI=${APP_URL}
LOGTO_AUTO_CREATE_USER=true
LOGTO_AUTO_UPDATE_USER=truephp artisan migrateThis will add the following fields to the v2_user table:
logto_sub: Logto user ID (unique identifier)auth_provider: Authentication provider ('local' or 'logto')
- Go to Logto Console or your self-hosted instance
- Navigate to Applications → Create application
- Select Traditional Web Application
- Choose PHP as the framework
Add the following URIs in your application settings:
Redirect URIs:
http://localhost:3000/api/v1/passport/auth/logto/callback
https://your-domain.com/api/v1/passport/auth/logto/callback
Post Sign-out Redirect URIs:
http://localhost:3000
https://your-domain.com
Copy the following from your Logto application:
- App ID →
LOGTO_APP_ID - App Secret →
LOGTO_APP_SECRET - Endpoint →
LOGTO_ENDPOINT
GET /api/v1/passport/auth/logto/sign-in
Returns the Logto sign-in URL.
Response:
{
"code": 0,
"data": {
"sign_in_url": "https://your-logto.app/oidc/auth?...",
"message": "Redirect to this URL to sign in with Logto"
}
}Frontend Usage:
const response = await fetch('/api/v1/passport/auth/logto/sign-in');
const { data } = await response.json();
window.location.href = data.sign_in_url;GET /api/v1/passport/auth/logto/callback
Processes the OIDC callback and returns user data with authentication token.
Query Parameters:
code: Authorization code from Logtostate: State parameter for CSRF protection
Response:
{
"code": 0,
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"uuid": "...",
"is_admin": false,
"balance": 0,
"transfer_enable": 0
},
"auth_data": "Bearer eyJ0eXAiOiJKV1QiLCJhbGc...",
"token": "abc123...",
"is_admin": false,
"message": "Authentication successful"
}
}POST /api/v1/passport/auth/logto/sign-out
Returns the Logto sign-out URL and clears local tokens.
Response:
{
"code": 0,
"data": {
"sign_out_url": "https://your-logto.app/oidc/session/end?...",
"message": "Redirect to this URL to complete sign-out"
}
}GET /api/v1/passport/auth/logto/userinfo
Returns current user information from both local database and Logto.
Response:
{
"code": 0,
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"balance": 0
},
"logto_user": {
"sub": "logto_user_id",
"email": "user@example.com",
"name": "John Doe",
"picture": "https://..."
}
}
}GET /api/v1/passport/auth/logto/check
Checks if the user is authenticated with Logto.
Response:
{
"code": 0,
"data": {
"is_authenticated": true,
"user_id": 1,
"email": "user@example.com",
"is_admin": false
}
}// composables/useLogtoAuth.ts
import { ref } from 'vue'
import axios from 'axios'
export function useLogtoAuth() {
const isAuthenticated = ref(false)
const user = ref(null)
async function signIn() {
const { data } = await axios.get('/api/v1/passport/auth/logto/sign-in')
window.location.href = data.data.sign_in_url
}
async function handleCallback() {
const { data } = await axios.get(
'/api/v1/passport/auth/logto/callback' + window.location.search
)
localStorage.setItem('auth_token', data.data.auth_data)
localStorage.setItem('user', JSON.stringify(data.data.user))
isAuthenticated.value = true
user.value = data.data.user
return data.data.user
}
async function signOut() {
const { data } = await axios.post('/api/v1/passport/auth/logto/sign-out')
localStorage.removeItem('auth_token')
localStorage.removeItem('user')
window.location.href = data.data.sign_out_url
}
return {
isAuthenticated,
user,
signIn,
signOut,
handleCallback
}
}<template>
<div class="login-page">
<h1>Welcome to Xboard</h1>
<button @click="handleLogin" class="btn-primary">
Sign in with Logto
</button>
</div>
</template>
<script setup lang="ts">
import { useLogtoAuth } from '@/composables/useLogtoAuth'
const { signIn } = useLogtoAuth()
function handleLogin() {
signIn()
}
</script><template>
<div class="callback-page">
<p>Signing in...</p>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useLogtoAuth } from '@/composables/useLogtoAuth'
const router = useRouter()
const { handleCallback } = useLogtoAuth()
onMounted(async () => {
try {
await handleCallback()
router.push('/')
} catch (error) {
console.error('Authentication failed', error)
router.push('/login')
}
})
</script>When LOGTO_AUTO_CREATE_USER=true, new users are automatically created in the local database on first login.
Default user attributes:
[
'transfer_enable' => 0,
'u' => 0,
'd' => 0,
'balance' => 0,
'commission_balance' => 0,
'expired_at' => null,
]When LOGTO_AUTO_UPDATE_USER=true, user information is updated from Logto on each login:
- Name
- Username
- Avatar (picture)
- Phone number
If a user with the same email exists in the local database (from traditional auth), they will be automatically linked to the Logto account on first Logto login.
| Field | Type | Description |
|---|---|---|
logto_sub |
VARCHAR(255) | Logto user ID (unique) |
auth_provider |
VARCHAR(20) | Authentication provider: 'local' or 'logto' |
idx_logto_subonlogto_subidx_auth_provideronauth_provider
- Logto tokens are stored in PHP session (configurable)
- Sanctum tokens are generated for API access
- Both token types are cleared on sign-out
- Logto users have
password = NULL - Password management is handled by Logto
- Local password authentication is still available for backward compatibility
- Logto uses
stateparameter for CSRF protection - Laravel CSRF middleware is applied to sign-out endpoint
Solution: Ensure the redirect URI in .env matches exactly with the one configured in Logto Console.
Solution: Check that:
- Database migration has been run
LOGTO_AUTO_CREATE_USERis set totrue- User table has
logto_subandauth_providercolumns
Solution: Verify:
- Logto credentials are correct
- Logto endpoint is accessible
- PHP session is working properly
Enable debug logging in .env:
APP_DEBUG=true
LOG_LEVEL=debugCheck logs in storage/logs/laravel.log for detailed error messages.
Add additional scopes in config/logto.php:
'scopes' => [
'openid',
'profile',
'email',
'phone',
'offline_access',
'roles', // For RBAC
'urn:logto:scope:organizations', // For organizations
],Configure API resources for access tokens:
'resources' => [
env('APP_URL') . '/api',
'https://api.example.com',
],Use custom storage instead of PHP session:
'storage' => 'cache', // or 'database'- Users with matching emails will be automatically linked
- First Logto login links the account
- Local password is preserved but not used
- Disable local registration in frontend
- Only show Logto sign-in button
- Keep local auth for admin accounts (optional)
- Visit
/api/v1/passport/auth/logto/sign-in - Copy the
sign_in_urland open in browser - Complete Logto authentication
- Verify callback creates user in database
- Check that Sanctum token works for API calls
# Get sign-in URL
curl http://localhost/api/v1/passport/auth/logto/sign-in
# Check auth status
curl http://localhost/api/v1/passport/auth/logto/checkFor issues or questions:
- Check Logto Documentation
- Review logs in
storage/logs/laravel.log - Open an issue on GitHub
This integration follows the same MIT license as Xboard.