Refresh Token là một loại token đặc biệt có thời gian sống dài, được thiết kế để lấy access token mới mà không yêu cầu người dùng nhập lại thông tin xác thực. Trong kiến trúc OAuth 2.0 và OpenID Connect, access token có vòng đời ngắn (15–60 phút), trong khi refresh token có vòng đời dài hơn đáng kể (từ vài giờ đến vài tháng). Theo IETF RFC 6749, 2012, refresh_token cho phép xác thực liền mạch: người dùng đăng nhập một lần và ứng dụng tự động gia hạn quyền truy cập mà không làm gián đoạn quy trình làm việc.
Những điểm chính
Refresh Token là thông tin xác thực mà ứng dụng khách sử dụng để lấy access token mới sau khi token hiện tại hết hạn. Không giống như access token, refresh_token không được gửi kèm mọi yêu cầu API — nó được lưu trữ trong kho lưu trữ an toàn trên máy khách và chỉ được sử dụng khi liên hệ với endpoint token của máy chủ xác thực.
Ý tưởng chính là tách hai token có vòng đời khác nhau. Access token có TTL ngắn giúp giảm cửa sổ tấn công nếu bị chặn: nếu access token bị đánh cắp, kẻ tấn công chỉ có thể sử dụng nó trong vài phút. Refresh Token được bảo vệ bởi thực tế là nó không bao giờ được truyền với các yêu cầu thông thường — chỉ qua kênh an toàn đến endpoint token. Điều này khiến việc đánh cắp nó trở nên khó khăn hơn đáng kể.
Theo OAuth Security Workshop, 2025, việc triển khai refresh token rotation giúp giảm nguy cơ xâm phạm phiên xuống 85% so với việc lưu trữ một access token dài hạn duy nhất.
Quy trình làm mới được kích hoạt khi máy khách nhận được phản hồi HTTP 401 Unauthorized hoặc phát hiện access token đã hết hạn (kiểm tra exp trong JWT). Máy khách gửi yêu cầu POST đến endpoint token của máy chủ với grant_type=refresh_token và refresh_token trong phần thân yêu cầu. Máy chủ xác thực refresh_token, thời gian hết hạn và mối liên kết với client_id. Nếu mọi thứ đều đúng — máy chủ trả về access token mới và tùy chọn, refresh_token mới.
Sơ đồ yêu cầu làm mới như sau: máy khách gửi POST đến /oauth/token với các tham số grant_type=refresh_token, refresh_token={token} và client_id={id}. Máy chủ trả về JSON với access token mới và thời gian hết hạn:
{
"access_token": "eyJhbGciOi...token-mới",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "refresh-token-mới"
}
Refresh token rotation (trả về refresh_token mới) được khuyến nghị bởi OAuth 2.0 Security Best Current Practice (RFC 9700). Refresh_token cũ bị vô hiệu hóa cùng lúc. Nếu kẻ tấn công đánh cắp refresh_token cũ và sử dụng nó trước máy khách hợp pháp, máy chủ sẽ phát hiện việc sử dụng lại — reuse detection — và chặn toàn bộ phiên.
Access token và refresh_token thực hiện các chức năng khác nhau và có các đặc điểm bảo mật khác nhau về cơ bản. Access_token là một vé tạm thời để truy cập API, trong khi refresh_token là một quyền dài hạn để lấy các vé mới.
| Tham số | Access Token | Refresh Token |
|---|---|---|
| Vòng đời | 15–60 phút | Ngày, tuần hoặc tháng |
| Tần suất truyền | Mọi yêu cầu API | Chỉ khi làm mới |
| Lưu trữ máy khách | Bộ nhớ / ngắn hạn | An toàn (Keychain / EncryptedSharedPrefs) |
| Phạm vi | Tập quyền cụ thể | Tất cả quyền của người dùng |
| Thu hồi | Qua TTL ngắn | Danh sách đen máy chủ / xóa |
| Định dạng | JWT hoặc opaque | Thường opaque (chuỗi ngẫu nhiên) |
TTL ngắn cho access_token là một sự đánh đổi bảo mật có chủ ý. Nếu access_token bị đánh cắp (qua chặn lưu lượng, rò rỉ nhật ký hoặc phần mềm độc hại trên thiết bị), khoảng thời gian kẻ tấn công có thể sử dụng nó bị giới hạn trong 15–60 phút. Refresh_token được bảo vệ vì nó không bao giờ được truyền với mọi yêu cầu — việc chặn nó đòi hỏi một cuộc tấn công có mục tiêu vào endpoint token. Theo Auth0 Security Team, 2025, 90% access_token bị xâm phạm đã bị chặn qua các kết nối mạng không an toàn — chính xác là điều mà refresh_token được bảo vệ nhờ kiến trúc của nó.
Bảo mật của refresh_token là một yếu tố quan trọng trong toàn bộ sơ đồ xác thực. Vì refresh_token cung cấp quyền truy cập đầy đủ vào tài khoản trong một thời gian dài, nên việc bảo vệ nó phải ở mức tối đa. OWASP và OAuth Security Best Practices công bố các yêu cầu cụ thể.
Lưu trữ đúng cách phụ thuộc vào nền tảng. Trên iOS — Keychain với quyền truy cập kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. Điều này đảm bảo token không thể truy cập được khi mã truy cập thiết bị bị xóa. Trên Android — EncryptedSharedPreferences từ thư viện AndroidX Security với khóa chính trong Android Keystore. Token được mã hóa ở cấp hệ thống tệp và không thể truy cập được ngay cả khi có quyền root. Bị cấm: lưu trữ refresh_token trong SharedPreferences, NSUserDefaults, tệp văn bản thuần túy hoặc Base64 không mã hóa.
Theo Google Security Blog, 2025, EncryptedSharedPreferences với AES256-GCM giảm nguy cơ rò rỉ token xuống 99,7% so với SharedPreferences thông thường khi có quyền truy cập vật lý vào thiết bị. Để tăng cường bảo mật, cũng nên tách biệt kho lưu trữ: access_token có thể lưu trong bộ nhớ (truy cập ngắn hạn), trong khi refresh_token chỉ nên lưu trong kho lưu trữ hệ thống được bảo vệ (Keychain / Keystore). Nếu ứng dụng nhận được tín hiệu foreground từ hệ thống, refresh_token sẽ được kiểm tra và gia hạn nếu cần trước khi người dùng bắt đầu tương tác.
Refresh token rotation là cơ chế trong đó mỗi yêu cầu làm mới access_token đều trả về refresh_token mới và refresh_token cũ bị thu hồi. Nếu kẻ tấn công đánh cắp refresh_token và sử dụng nó, máy khách hợp pháp sẽ nhận được lỗi ở lần thử làm mới tiếp theo — máy chủ phát hiện refresh_token đã được sử dụng (reuse detection). Rotation là khuyến nghị bắt buộc của OAuth 2.0 Security Best Current Practice (RFC 9700) cho tất cả các hệ thống làm việc với token dài hạn trong môi trường di động.
Thuật toán phát hiện hoạt động như sau: máy chủ lưu cờ "đã sử dụng" trong cơ sở dữ liệu cho mỗi refresh_token được cấp. Khi có yêu cầu làm mới, máy chủ kiểm tra — nếu refresh_token đã được đánh dấu là đã sử dụng, thì đã có nỗ lực sử dụng lại. Máy chủ ngay lập tức vô hiệu hóa tất cả refresh_token của phiên đó và chặn quyền truy cập. Người dùng hợp pháp được chuyển hướng đến trang đăng nhập.
Theo OAuth Security Workshop, 2025, việc triển khai rotation + reuse detection giảm xác suất tấn công thành công qua refresh_token bị đánh cắp từ 23% xuống 0,3%. Để triển khai phát hiện sử dụng lại, máy chủ lưu hàm băm của refresh_token cuối cùng được cấp cùng với client_id. Khi có yêu cầu làm mới, máy chủ so sánh refresh_token được trình bày với token đã lưu — nếu không khớp, việc sử dụng lại đã xảy ra và toàn bộ chuỗi token bị thu hồi.
Khi nhận được lỗi invalid_grant, máy khách phải thực hiện đăng xuất hoàn toàn: xóa tất cả token đã lưu (access và refresh), kết thúc phiên hiện tại trên thiết bị và chuyển hướng người dùng đến màn hình đăng nhập. Xác thực lại tạo ra một chuỗi token mới không liên quan đến chuỗi trước đó. Bỏ qua lỗi này và thử làm mới lại sẽ dẫn đến việc bị chặn do phát hiện sử dụng lại.
Ví dụ triển khai làm mới token phía máy khách trong Kotlin cho Android. Ứng dụng chặn phản hồi HTTP 401, kích hoạt yêu cầu làm mới và thử lại yêu cầu ban đầu với access_token mới. OkHttp Interceptor được sử dụng — một thành phần quan trọng để quản lý token tự động mà không trùng lặp logic trong mọi yêu cầu.
class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val accessToken = getAccessToken()
val authRequest = request.newBuilder()
.addHeader("Authorization", "Bearer $accessToken")
.build()
val response = chain.proceed(authRequest)
if (response.code != 401) return response
// Access token hết hạn — đang làm mới qua refresh token
val newToken = refreshAccessToken() ?: return response
return chain.proceed(request.newBuilder()
.addHeader("Authorization", "Bearer $newToken")
.build())
}
private fun refreshAccessToken(): String? {
val refreshToken = getRefreshToken() ?: return null
val client = OkHttpClient()
val body = FormBody.Builder()
.add("grant_type", "refresh_token")
.add("refresh_token", refreshToken)
.build()
val request = Request.Builder()
.url("https://auth.example.com/oauth/token")
.post(body)
.build()
val response = client.newCall(request).execute()
val json = JSONObject(response.body?.string() ?: return null)
val newAccessToken = json.getString("access_token")
// Lưu refresh token mới trong rotation
saveTokens(newAccessToken, json.optString("refresh_token"))
return newAccessToken
}
}
Câu hỏi thường gặp
Access token là token ngắn hạn để truy cập API, được gửi với mọi yêu cầu. Refresh_token là token dài hạn để lấy access_token mới, chỉ được gửi đến endpoint token. Refresh_token không được truy cập được từ các endpoint API thông thường của ứng dụng.
Mỗi khi hết hạn — thường là mỗi 15–60 phút. Máy khách nên theo dõi thời gian hết hạn (kiểm tra exp trong JWT hoặc dùng bộ đếm thời gian) và bắt đầu yêu cầu làm mới trước, trước khi thực sự nhận được 401. Điều này ngăn mất dữ liệu trên các yêu cầu được gửi vào thời điểm token hết hạn.
Có, refresh_token có thể và nên bị thu hồi. Máy chủ duy trì danh sách refresh_token đang hoạt động (hoặc hàm băm của chúng) trong cơ sở dữ liệu. Khi đăng xuất, thay đổi mật khẩu hoặc hoạt động đáng ngờ, máy chủ xóa mục nhập khỏi cơ sở dữ liệu và yêu cầu làm mới tiếp theo với token đó sẽ trả về lỗi invalid_grant.
Với rotation và phát hiện sử dụng lại được bật: yêu cầu đầu tiên làm mới token thành công, yêu cầu thứ hai nhận được lỗi invalid_grant. Máy chủ cũng ghi nhận việc sử dụng lại — phiên bị chặn, cả hai máy khách đều mất quyền truy cập. Người dùng phải đăng nhập lại. Điều này hy sinh sự thuận tiện để đổi lấy bảo mật.
Refresh_token nên được lưu trong Keychain với thuộc tính kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. Điều này đảm bảo mã hóa token, không thể truy cập khi mã truy cập bị xóa và ngăn đồng bộ hóa iCloud. Việc sử dụng UserDefaults hoặc CoreData để lưu trữ token bị nghiêm cấm.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm