Tài liệu
Code Mẫu
Ví Dụ Code
Hướng dẫn tích hợp OAuth2 cho các kịch bản thực tế khác nhau.
Ứng Dụng Web Truyền Thống
Website PHP truyền thống với session server-side.
Tệp: login.php
<?php
session_start();
require_once 'config.php';
// Tạo state token
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;
// Xây dựng URL phân quyền
$auth_url = APP_URL . '/authorize.php?' . http_build_query([
'client_id' => CLIENT_ID,
'response_type' => 'code',
'redirect_uri' => REDIRECT_URI,
'scope' => 'openid profile email',
'state' => $state
]);
header('Location: ' . $auth_url);
exit;
Tệp: callback.php
<?php
session_start();
require_once 'config.php';
// Kiểm tra state
if (!hash_equals($_SESSION['oauth_state'] ?? '', $_GET['state'] ?? '')) {
die('Lỗi: State không khớp');
}
// Trao đổi code lấy token
$token_response = file_get_contents(APP_URL . '/api/token', false,
stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query([
'grant_type' => 'authorization_code',
'code' => $_GET['code'],
'client_id' => CLIENT_ID,
'client_secret' => CLIENT_SECRET,
'redirect_uri' => REDIRECT_URI
])
]
])
);
$tokens = json_decode($token_response, true);
if (!$tokens || isset($tokens['error'])) {
die('Lỗi: Không lấy được token');
}
// Lưu token vào session
$_SESSION['access_token'] = $tokens['access_token'];
$_SESSION['refresh_token'] = $tokens['refresh_token'] ?? null;
// Lấy thông tin người dùng
$userinfo = file_get_contents(APP_URL . '/api/userinfo', false,
stream_context_create([
'http' => [
'header' => 'Authorization: Bearer ' . $tokens['access_token']
]
])
);
$user = json_decode($userinfo, true);
$_SESSION['user'] = $user;
header('Location: dashboard.php');
exit;
Single Page Application (React)
Ứng dụng React với PKCE flow (bảo mật hơn cho SPA).
Tệp: useOAuth.js
import { useState } from 'react';
export function useOAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const generatePKCE = async () => {
const verifier = generateRandomString(43);
const challenge = await sha256(verifier);
localStorage.setItem('pkce_verifier', verifier);
return challenge;
};
const login = async () => {
setLoading(true);
try {
const state = generateRandomString(16);
const challenge = await generatePKCE();
localStorage.setItem('oauth_state', state);
const authUrl = `${process.env.REACT_APP_AUTH_URL}/authorize.php?` +
new URLSearchParams({
client_id: process.env.REACT_APP_CLIENT_ID,
response_type: 'code',
redirect_uri: window.location.origin + '/callback',
scope: 'openid profile email',
state,
code_challenge: challenge,
code_challenge_method: 'S256'
});
window.location.href = authUrl;
} catch (err) {
setError(err.message);
setLoading(false);
}
};
return { user, loading, error, login };
}
Mobile API (Native/Flutter)
Ứng dụng di động gọi API backend.
Tệp: lib/services/auth_service.dart
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class AuthService {
final _appAuth = FlutterAppAuth();
final _storage = FlutterSecureStorage();
static const _clientId = 'mobile_app';
static const _redirectUrl = 'com.myapp:/oauth-callback';
static const _discoveryUrl = 'https://auth.example.com';
Future<bool> login() async {
try {
final result = await _appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
_clientId,
_redirectUrl,
discoveryUrl: _discoveryUrl,
scopes: ['openid', 'profile', 'email'],
promptValues: ['login'],
),
);
if (result != null) {
await _storage.write(
key: 'access_token',
value: result.accessToken,
);
await _storage.write(
key: 'refresh_token',
value: result.refreshToken ?? '',
);
return true;
}
return false;
} catch (e) {
print('Lỗi OAuth: $e');
return false;
}
}
}
Microservices (Node.js)
Service-to-service authentication với client credentials flow.
Tệp: auth-client.js
const axios = require('axios');
class ServiceAuthClient {
constructor(clientId, clientSecret, authUrl) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.authUrl = authUrl;
this.accessToken = null;
this.tokenExpiry = null;
}
async getToken() {
if (this.accessToken && this.tokenExpiry > Date.now()) {
return this.accessToken;
}
try {
const response = await axios.post(
`${this.authUrl}/api/token`,
{
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
scope: 'service:read service:write'
}
);
this.accessToken = response.data.access_token;
this.tokenExpiry = Date.now() + response.data.expires_in * 1000;
return this.accessToken;
} catch (error) {
throw new Error(`Lỗi lấy token: ${error.message}`);
}
}
async request(method, url, data = null) {
const token = await this.getToken();
return axios({
method,
url,
data,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
}
}
module.exports = ServiceAuthClient;
Background Job (Python)
Python script cho công việc background.
Tệp: jobs/data_sync.py
import requests
import json
from datetime import datetime, timedelta
class OAuthClient:
def __init__(self, client_id, client_secret, auth_url):
self.client_id = client_id
self.client_secret = client_secret
self.auth_url = auth_url
self.token = None
self.token_expiry = None
def get_token(self):
if self.token and self.token_expiry > datetime.now():
return self.token
try:
response = requests.post(
f'{self.auth_url}/api/token',
data={
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret
}
)
response.raise_for_status()
data = response.json()
self.token = data['access_token']
self.token_expiry = datetime.now() + timedelta(
seconds=data['expires_in']
)
return self.token
except requests.RequestException as e:
raise Exception(f'Lỗi OAuth: {e}')
def get_user_data(self, user_id):
token = self.get_token()
response = requests.get(
f'{self.auth_url}/api/users/{user_id}',
headers={'Authorization': f'Bearer {token}'}
)
response.raise_for_status()
return response.json()
Public API Client
Client công khai với giới hạn tỷ lệ.
Tệp: api-client.js
class PublicAPIClient {
constructor(apiUrl, clientId, clientSecret) {
this.apiUrl = apiUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.rateLimitRemaining = null;
this.rateLimitReset = null;
}
async request(endpoint, method = 'GET', data = null) {
try {
const options = {
method,
headers: {
'Authorization': `Basic ${btoa(
this.clientId + ':' + this.clientSecret
)}`,
'Content-Type': 'application/json'
}
};
if (data) {
options.body = JSON.stringify(data);
}
const response = await fetch(
`${this.apiUrl}${endpoint}`,
options
);
this.rateLimitRemaining = response.headers.get('X-RateLimit-Remaining');
this.rateLimitReset = response.headers.get('X-RateLimit-Reset');
if (response.status === 429) {
throw new Error(
`Vượt giới hạn tỷ lệ. Thử lại sau ${this.rateLimitReset}s`
);
}
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Lỗi API:', error);
throw error;
}
}
async getUsers() {
return this.request('/api/users');
}
async getUserData(userId) {
return this.request(`/api/users/${userId}`);
}
getRateLimitStatus() {
return {
remaining: this.rateLimitRemaining,
resetIn: this.rateLimitReset
};
}
}
Error Handling Pattern
Xử lý lỗi toàn cầu cho tất cả trường hợp.
Tệp: error-handler.js
class OAuthErrorHandler {
static getErrorMessage(error) {
if (error.error === 'invalid_token') {
return 'Token không hợp lệ hoặc hết hạn. Vui lòng đăng nhập lại.';
}
if (error.error === 'expired_token') {
return 'Token đã hết hạn. Đang làm mới...';
}
if (error.error === 'access_denied') {
return 'Bạn đã từ chối cấp quyền.';
}
if (error.error === 'invalid_scope') {
return 'Quyền hạn không được hỗ trợ.';
}
if (error.error === 'invalid_client') {
return 'Ứng dụng không hợp lệ. Liên hệ với admin.';
}
if (error.error === 'invalid_request') {
return 'Yêu cầu không hợp lệ: ' + (error.error_description || '');
}
return 'Lỗi xác thực. Vui lòng thử lại.';
}
static async handleTokenRefresh(refreshToken, authUrl, clientId) {
try {
const response = await fetch(`${authUrl}/api/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId
})
});
if (!response.ok) {
throw new Error('Refresh token hết hạn');
}
return await response.json();
} catch (error) {
console.error('Lỗi làm mới token:', error);
return null;
}
}
}
module.exports = OAuthErrorHandler;
Mẹo: Sử dụng error handler này cho tất cả OAuth operations để cung cấp trải nghiệm người dùng tốt hơn.
Cập nhật gần nhất: Tháng 10, 2025
Lên đầu trang