本系列文之後也會置於個人網站
+----------+ +----------------+
| |>---(A)-- Client Identifier --->| |
| | | |
| |<---(B)-- Device Code, ---<| |
| | User Code, | |
| Device | & Verification URI | |
| Client | | |
| | [polling] | |
| |>---(E)-- Device Code --->| |
| | & Client Identifier | |
| | | Authorization |
| |<---(F)-- Access Token ---<| Server |
+----------+ (& Optional Refresh Token) | |
v | |
: | |
(C) User Code & Verification URI | |
: | |
v | |
+----------+ | |
| End User | | |
| at |<---(D)-- End user reviews --->| |
| Browser | authorization request | |
+----------+ +----------------+
Figure 1: Device Authorization Flow
The device authorization flow illustrated in Figure 1 includes the
following steps:
(A) The client requests access from the authorization server and
includes its client identifier in the request.
(B) The authorization server issues a device code and an end-user
code and provides the end-user verification URI.
(C) The client instructs the end user to use a user agent on another
device and visit the provided end-user verification URI. The
client provides the user with the end-user code to enter in
order to review the authorization request.
Device Code Flow這個與前面幾個特別不一樣。在之前,以往都是從登入開始,然後跳轉頁面回到App(Client)。也就是通常先有的是前端通訊,然後才是後端通信。
這次不太同,開始不再是由資源擁有者發起,更像是由客戶端開始。甚至登入的方法與客戶端還沒有特別強烈的關聯。大致流程說明如下:
device_code
和user_code
。user_code
交給資源擁有者。user_code
進行授權。device_code
詢問授權伺服器是某有人授權。在這裡device_code
有點更像是傳統的session
。user_code
更像是之前的特殊密碼。
硬要比喻的話,就像是有一個充滿智慧的門,就先叫做「魔門」吧!這個魔門不但聰明還會識別人臉。它知道未來有一天,一定會有一個人來開啟它這扇門,但他不知道是誰。於是他把一段咒語--「魔門阿!魔門!誰是世界上最安全的鎖」--告訴管家。如果有一天有一個人將這個咒語告訴了管家,那就請管家將這個人的臉紀錄下來,並告訴魔門。當這個人走到魔門前面的時候,魔門就認得這張人臉,也就會自動開門了。
怕有人不知道。「魔鏡」的故事聽過吧XD
雖然這個比喻並不完全匹配,但是這裡的「魔鏡」有點像是客戶端;「管家」仍然是授權伺服器。與之前幾個流程相比,有點像是反過來走。
example-device-app
由於OAuth Playground和OAuth.tools都不太能很好的用來理解這個模式。所以這一個會分成兩個部分介紹,除了實際走走上面提到的流程,還會在實際開發一個簡易的客戶端。如此,希望能夠使各位能夠更明白Device Code Flow。
那麼就先來再建立一個客戶端。
儘管用之前的「oauth_tools」或是調整「my-quick-start-app」也可以。不過還是從頭來一遍吧!
example-device-app
http://localhost:4200/
雖然這次Root URL並不是很重要,但還是填上與「my-quick-start-app」相同的值吧!
然後注意這次的Access Type需要選擇confidential
並且將Oauth 2.0 Device Authorization Grant啓用。
然後先將 Credentials > Secret 記下來。
這麼一來前置工作就算是準備好了。
device_code
和user_code
這次要用HTTP POST的方法打http://localhost:8080/auth/realms/quick-start/device
這個端點。
這次需要配合 Client Credentials 模式。所以不但要給client_id
,也需要client_secret
。client_secret
填入剛剛所記下來的secret
。
example-device-app
<方才記錄下來的secret>
在送出request後會得到一些資訊。
{
"device_code": "boTQ6vd49RXTOYOb7dwXBCpHYskzOjXvDPjkXxniMN0",
"user_code": "HZYO-ROXJ",
"verification_uri": "http://localhost:8080/auth/realms/quick-start/device",
"verification_uri_complete": "http://localhost:8080/auth/realms/quick-start/device?user_code=HZYO-ROXJ",
"expires_in": 600,
"interval": 5
}
其中最重要的是device_code
、user_code
和verification_uri
。
到這邊算是初步完成了,但你可以先透過token_endpoint
,也就是http://localhost:8080/auth/realms/quick-start/protocol/openid-connect/token
這個端點確定一下。
除了需要給與device_code以外,還需要將grant_type改成urn:ietf:params:oauth:grant-type:device_code
。
urn:ietf:params:oauth:grant-type:device_code
<方才取得的device_code>
現在,你應該會得到仍未有人登入授權。
魔門 真是孤獨阿~
{
"error": "authorization_pending",
"error_description": "The authorization request is still pending"
}
接著,同樣透過剛剛知道的端點 verification_uri 登入。瀏覽器開啓 http://localhost:8080/auth/realms/quick-start/device ,並輸入同樣剛才得到的user_code:
或者是一個特殊的端點http://localhost:8080/auth/realms/quick-start/device?user_code=<user_code>
。但總之登入後的是一個授權畫面。
在看下Yes允許授權後,提示的是登入成功,我們可以回到我們的應用。
同樣在嘗試取得一次存取權杖看看:
urn:ietf:params:oauth:grant-type:device_code
<方才取得的device_code>
這次我們就可以取得存取權杖了~
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4VXh6WGR4UWpFNDNIZGdYbXJkUjBQZWxXN1ZoZWowbGRkR2NhN0VubXpZIn0.eyJleHAiOjE2MzI1Nzg5NTQsImlhdCI6MTYzMjU3ODY1NCwiYXV0aF90aW1lIjoxNjMyNTc3NTIxLCJqdGkiOiI5YTBhYTdkZi02NmEyLTRlMDgtYmZmNS05MjU2NDZlYmM1MTkiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvcXVpY2stc3RhcnQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMzQ1YjFiY2EtOTgwNS00YjRiLWEwZjgtZGEyYzcwMTc2YzU5IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZXhhbXBsZS1kZXZpY2UtYXBwIiwic2Vzc2lvbl9zdGF0ZSI6ImFmODhlZmZiLTVmNDgtNDgxZi04YWI2LTQ5MGRhMjY2YzY1ZCIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDo0MjAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXF1aWNrLXN0YXJ0Iiwib2ZmbGluZV9hY2Nlc3MiLCJxdWljay1zdGFydC1leGFtcGxlLXJvbGUxIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJhZjg4ZWZmYi01ZjQ4LTQ4MWYtOGFiNi00OTBkYTI2NmM2NWQiLCJ3ZWJzaXRlIjoiaHR0cHM6Ly9ib2IuaWQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZ2VuZGVyIjoibWFuIiwibmFtZSI6IkJvYiBMZWUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJib2IiLCJnaXZlbl9uYW1lIjoiQm9iIiwiZmFtaWx5X25hbWUiOiJMZWUiLCJlbWFpbCI6ImJvYkBmYWtlLmVtYWlsIn0.aPYPiG9icNnrxmmRUPoN_rf2eLhkprK7haXD9M6CpGRAHnjyzOkH1H2an3Au2I4LgFX7fdO95E5mF7NZ4gw-5SFe9Wof3Toe8araIQepurJXMd9Vx9Y6cO-htha796rkpUq2XNkcBLRHl9bN2zN3-2VM1X1pBqQPtDPeGckkp_2KH8u8n9UPxdq_lD-FZ_3-YzQav1RTs81QkxEBTn8Un7mzgRjdsmIkEJKYQqnhg86Xkw_bAGz-F-nDoj1XtHqjSIk_1EZ9brcgi05us-9ZL0tBViycQRCXEgJjq555omxh1XgjjuC_KPzA3y7hTTE75ZvO-3rc0sVy1DC-1AEtbg",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0NjUwNDBkYi1lNGJkLTRiYTYtOWM2Ny02ZWYxZGJmMmUxOWYifQ.eyJleHAiOjE2MzI1ODA0NTQsImlhdCI6MTYzMjU3ODY1NCwianRpIjoiZGI3NTBjOTMtODg3Mi00YmUxLTk3YWUtMmU2MTMxNzNjMDU2IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3F1aWNrLXN0YXJ0IiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3F1aWNrLXN0YXJ0Iiwic3ViIjoiMzQ1YjFiY2EtOTgwNS00YjRiLWEwZjgtZGEyYzcwMTc2YzU5IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImV4YW1wbGUtZGV2aWNlLWFwcCIsInNlc3Npb25fc3RhdGUiOiJhZjg4ZWZmYi01ZjQ4LTQ4MWYtOGFiNi00OTBkYTI2NmM2NWQiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJhZjg4ZWZmYi01ZjQ4LTQ4MWYtOGFiNi00OTBkYTI2NmM2NWQifQ.1NGocwCZbpT-OjKnVj420Vql1_C3iwcZavuwJEk2sk0",
"token_type": "Bearer",
"not-before-policy": 1631743594,
"session_state": "af88effb-5f48-481f-8ab6-490da266c65d",
"scope": "email profile"
}