[Auth] OAuth2
OAuth là một phương thức chứng thực giúp các ứng dụng có thể chia sẻ tài nguyên với nhau mà không cần chia sẻ thông tin username và password.
Từ Auth ở đây mang 2 nghĩa:
- Authentication: xác thực người dùng thông qua việc đăng nhập.
- Authorization: cấp quyền truy cập vào các Resource.
Bài viết chỉ muốn tổng hợp lại 2 mô hình của OAuth, một mô hình cơ bản và một mô hình cho cách cấp ủy quyền được sử dụng phổ biến nhất: Authorization Code Grant
Mô hình chung
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
Ref: https://tools.ietf.org/html/rfc6749
Một vài điểm chú ý về sơ đồ này
Resource owner
là người dùng, chính là bạnClient
là application bên thứ 3, bạn có thể hình dung là 1 ứng dụng sử dụng facebook api chẳng hạnAuthrization Server
vàResource Server
là những dịch vụ mà phía bên facebook hay twitter phải implement để thực hiện oauth.- Đây chính là điểm cơ bản tạo nên khác biệt giữa Oauth2 và Oauth1, khi “tách biệt” giữa việc chứng thực (authorization) , và việc cung cấp thông tin người dùng (resource) thành 2 server riêng.
- Mục đích cuối cùng là lấy được access_token để truy cập tài nguyên bên bạn cần đăng nhập (Facebook chẳng hạn), tất nhiên, token này có thể bị hạn chế 1 số quyền hơn khi bạn sử dụng Facebook thật.
Dựa vào cách cấp
access_token
khác nhau mà ta có các loại triển khai OAuth2 khác nhau
- Bước Authorization Grant (C và D) tùy vào loại OAuth2 bạn triển khai mà nó có thể có hoặc không :D
Các cách cấp ủy quyền
Như tôi đã nói ở trên, tùy cách cấp phát access_token
mà chúng ta có các cách cấp ủy quyền khác nhau. Về cơ bản sẽ có 4 cách, tuy nhiên cách đầu tiên Authorization Code được sử dụng phổ biến nhất. Ba cách cấp ủy quyền còn lại ít được sử dụng.
Authorization Code Grant
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
Giả định
- User Agent là bạn
- Client là ứng dụng thứ 3 - XXX, cần lấy tài nguyên qua Facebook của bạn (name + avatar + email chẳng hạn). Các thông tin này được lưu ở
- Authorization Server (Facebook Resource) và cần đăng nhập từ
- Resource Owner (Facebook OAuth API) (Hình như Facebook đang để Facebook Resource và Facebook OAuth API là 1, nhưng ví dụ này tôi cứ để tách ra cho rõ).
Hình minh họa trên được mô tả như sau
- (A) + (B) Bạn vào trang XXX, click đăng nhập với Facebook, và được chuyển hướng đến trang
1
2
3
4
5https://Facebook-OAuth-API.DOMAIN/authorize
?response_type=token
&client_id=CLIENT_ID
&redirect_uri=CALLBACK_URL
&scope=read- response_type=token => Ý bảo với ứng dụng Facebook OAuth API là cấp mã ủy quyền
- redirect_uri => Sau khi Facebook OAuth API xác thực các thông tin là đúng, chuyển token về URI này cho tôi nhé
Tất nhiên URL ở trên cần có middleware Auth để yêu cầu người dùng nhập tài khoản facebook mới xử lý tiếp :D
(C): Tiếp theo phần trên, khi có mã ủy quyền (token), Facebook OAuth API sẽ chuyển về url XXX và bên này cần có router đón nó
1
https://XXX.DOMAIN/callback?code=AUTHORIZATION_CODE
(D): Sau khi có ACCESS_TOKEN, XXX cần gửi tiếp 1 request đến Facebook, nhưng lần này là Facebook Resoure để lấy
access_token
1
2
3
4
5https://Facebook-Resource.com/oauth/token
?client_id=CLIENT_ID&client_secret=CLIENT_SECRET
&grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=CALLBACK_URL- grant_type => cách cấp access_token, ở đây là authorization_code
- redirect_uri => khi Facebook Resource kiểm tra các thông tin đúng, gửi access_token cho tôi về địa chỉ này nhé, thường sẽ là về lại trang XXX
- (E): XXX nhận
access_token
qua redirect_uri ở bước D thôi. Từ access_token này, bạn có thể gọi các API lấy thông tin name, avatar, email của user qua Facebook được rồi đó :D
Như vậy, cả client (ứng dụng của bạn) và ứng dụng thứ 3 (Facebook đều phải triển khai OAuth). Các URL của 2 bên1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16(1) https://Facebook-OAuth-API.DOMAIN/authorize
?response_type=token
&client_id=CLIENT_ID
&redirect_uri=CALLBACK_URL
&scope=read
=> Middlware Auth
(2) https://XXX.DOMAIN/callback?code=AUTHORIZATION_CODE
=> Bên XXX xử lý để nhận code từ bước 1
(3) https://Facebook-Resource.com/oauth/token
?client_id=CLIENT_ID&client_secret=CLIENT_SECRET
&grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=CALLBACK_URL
=> Lấy access_token
Áp dụng OAuth2 trong Laravel Passport: (Ứng dụng của bạn chính là Client - XXX)
Bước ấn nút chuyển hướng đăng nhập bằng Facebook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$request->session()->put('code_verifier', $code_verifier = Str::random(128));
$codeChallenge = strtr(rtrim(
base64_encode(hash('sha256', $code_verifier, true))
, '='), '+/', '-_');
$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://example.com/callback',
'response_type' => 'code',
'scope' => '',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
]);
return redirect('http://your-app.com/oauth/authorize?'.$query);
});Đây là bước request access_token (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 Route::get('/callback', function (Request $request) {
$state = $request->session()->pull('state');
$codeVerifier = $request->session()->pull('code_verifier');
throw_unless(
strlen($state) > 0 && $state === $request->state,
InvalidArgumentException::class
);
$response = (new GuzzleHttp\Client)->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'redirect_uri' => 'http://example.com/callback',
'code_verifier' => $codeVerifier,
'code' => $request->code,
],
]);
return json_decode((string) $response->getBody(), true);
});
Password Grant Tokens
- Cung cấp cả thông tin username + password (tài khoản facebook của bạn), sử dụng khi thật tin tưởng
- Do có cả username và password nên bạn gọi thẳng 1 API vào Authorization Server để xin cấp access_token
1
2
3
4
5
6
7
8
9$response = (new GuzzleHttp\Client)->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'redirect_uri' => 'http://example.com/callback',
'code_verifier' => $codeVerifier,
'code' => $request->code,
],
]);
Client Credentials
Sinh token từ chính client_id và secret_id
Device Code
Tài liệu đọc thêm
[Auth] OAuth2