Refresh Token cho ứng dụng di động — bản chất, cơ chế làm mới và lưu trữ an toàn

Tác giả: IT Sectr Đã đăng: 2026-04-05 Thời gian đọc: 9 phút

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 — token dài hạn để lấy access token mới mà không cần đăng nhập lại
  • Access token ngắn hạn — giảm rủi ro khi rò rỉ: kẻ tấn công có quyền truy cập trong 15–30 phút
  • Token rotation — mỗi yêu cầu làm mới trả về refresh token mới, token cũ bị vô hiệu hóa
  • Lưu trữ an toàn — iOS Keychain, Android EncryptedSharedPreferences, không bao giờ trong NSUserDefaults
  • Phát hiện sử dụng lại refresh token — bảo vệ chống trộm: nếu refresh token bị đánh cắp được sử dụng, phiên sẽ bị chặn

Refresh Token là gì?

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.

Cách hoạt động của Refresh Token

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.

Luồng làm mới token

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}client_id={id}. Máy chủ trả về JSON với access token mới và thời gian hết hạn:

json
{
  "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.

Refresh Token so với Access Token

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 TokenRefresh Token
Vòng đời15–60 phútNgày, tuần hoặc tháng
Tần suất truyềnMọi yêu cầu APIChỉ khi làm mới
Lưu trữ máy kháchBộ nhớ / ngắn hạnAn toàn (Keychain / EncryptedSharedPrefs)
Phạm viTập quyền cụ thểTất cả quyền của người dùng
Thu hồiQua TTL ngắnDanh sách đen máy chủ / xóa
Định dạngJWT hoặc opaqueThường opaque (chuỗi ngẫu nhiên)

Tại sao access_token không thể có thời gian sống dài

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 Refresh Token

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ữ refresh token trên thiết bị di động

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

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.

Phát hiện sử dụng lại

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.

Triển khai trong Kotlin

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.

kotlin
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

Refresh token khác access token như thế nào?

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.

Bao lâu nên làm mới access token một lần?

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ó thể thu hồi refresh token trên máy chủ không?

, 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.

Điều gì xảy ra nếu hai máy khách cùng sử dụng refresh token cũ?

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.

Nên lưu trữ refresh token an toàn ở đâu trên iOS?

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

  • Refresh Token — token dài hạn để làm mới access_token mà không cần đăng nhập lại
  • TTL ngắn của access_token (15–60 phút) giảm thiểu thiệt hại do rò rỉ
  • Token rotation — mỗi lần làm mới trả về refresh_token mới, token cũ bị vô hiệu hóa
  • Phát hiện sử dụng lại — phát hiện token bị đánh cắp và chặn phiên
  • Lưu trữ — iOS Keychain, Android EncryptedSharedPreferences (AES256-GCM)
  • Thu hồi máy chủ — xóa refresh_token khỏi cơ sở dữ liệu khi đăng xuất hoặc đổi mật khẩu
  • Refresh token không bao giờ được truyền với các yêu cầu API thông thường

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.

Thảo luận dự án

Đọc thêm