作者 | Robin Guldener
策劃 | 言征
OAuth 是一個標準協議。基本上你可以想象的每種編程語言都有 OAuth 2.0 的客戶端庫。
但有了客戶端庫,卻并不意味著萬事大吉,如果你能夠在大約 10 分鐘內做到為任何 API 實施 OAuth。或者至少在一個小時內,請給我們發電子郵件——我們想請你吃一頓美味的晚餐,并聽聽你是怎么做到的。
1、50 個 OAuth 實踐:結果一團糟
我們為 50 個最流行的 API 實現了 OAuth,例如 google(Gmail、Calendar、Sheets 等)、HubSpot、Shopify、Salesforce、Stripe、Jira、Slack、Microsoft(Azure、Outlook、OneDrive)、LinkedIn、Facebook 和其他 OAuth API等。
我們的結論:現實世界的 OAuth 體驗可與 2008 年的 JAVAScript 瀏覽器 API 相媲美。人們普遍認為應該如何做事,但實際上每個 API 都有自己對標準、實現怪癖以及非標準行為和擴展的解釋. 結果:到處都是坑。
2、OAuth 標準太大太復雜
“這個 API 也使用 OAuth 2.0,我們幾周前就已經這樣做了。我應該在明天之前完成。”
——實習生的著名遺言
OAuth 是一個非常大的標準。OAuth 2.0 的官方網站目前列出了 17 個不同的 RFC(定義標準的文檔),它們共同定義了 OAuth 2 的工作方式。它們涵蓋了從 OAuth 框架和 Bearer 令牌到威脅模型和私鑰 JWT 的所有內容。
“但是,”我聽到你說,“肯定不是所有這些 RFC 都與使用 API 的簡單第三方訪問令牌授權相關嗎?”
你說得對。讓我們只關注可能與典型的 API 第三方訪問用例相關的事情:
OAuth 標準:OAuth 2.0 現在是默認的,但是 OAuth 1.0a 仍然被一些人使用(2.1 即將到來)。一旦你知道你的 API 使用了哪一個,請繼續。
授予類型:你需要authorization_code、client_credentials還是device_code?它們的作用是什么,你應該在什么時候使用它們?如有疑問,請嘗試authorization_code。
旁注:刷新令牌也是一種授權類型,但有點特殊。它們的工作方式是標準化的,但你最初要求它們的方式卻不是。稍后會詳細介紹。
現在你已準備好處理你的請求,讓我們看看許多(準確地說是 72 個)具有定義的含義和行為的官方 OAuth 參數。常見示例有prompt、scope、audience、resource、assertion和login_hint。然而,根據我們的經驗,大多數 API 提供者似乎都沒有注意到這個列表,就像你可能直到現在一樣,所以不要太擔心它。
如果你認為這仍然感覺太復雜并且需要學習很多東西,我們傾向于同意你的看法。
大多數構建公共 API 的團隊似乎也同意這一點。他們沒有實現完整的 OAuth 2.0 子集,而是只實現了他們認為 API 用例所需的 OAuth 部分。這導致文檔中有相當長的頁面概述了 OAuth 如何為這個特定的 API 工作。但是我們很難責怪他們;他們的 DX 只考慮最好的意圖。如果他們真的試圖實施完整的標準,你需要閱讀一本小書!
Salesforce authorization_code OAuth 流程。為什么不喜歡這個簡單的 10 步過程的清晰視覺效果?
問題在于每個人對于 OAuth 的哪個子集與他們相關的想法略有不同,因此你最終會得到許多不同的(子)實現。
3、每個人的 OAuth 都有細微差別
由于每個 API 都實現了不同的 OAuth 子集,你很快就會陷入被迫詳細閱讀 OAuth 文檔的長頁的情況:
他們在授權調用中需要哪些參數?
- 對于 Jira,audience參數是 key(并且必須設置為特定的固定值)。谷歌更喜歡通過不同的范圍來處理這個問題,但真正關心的是提示參數。同時,Microsoft 的某個人發現了response_mode參數并要求你始終將其設置為query。
- Notion API 采用了一種激進的方法,取消了無處不在的范圍參數。事實上,你甚至不會在他們的 API 文檔中找到“作用域”這個詞。Notion 將它們稱為“功能”,你可以在注冊應用程序時設置它們。我們花了 30 分鐘才明白發生了什么。他們為什么要重新發明這個輪子?
- 使用offline_access會變得更糟:現在大多數 API 都會在短時間后過期訪問令牌。要獲得刷新令牌,你需要請求“offline_access”,這需要通過參數、范圍或你在注冊 OAuth 應用程序時設置的內容來完成。有關詳細信息,請咨詢你的 API 或 OAuth 醫生。
他們希望在令牌請求調用中看到什么?
- 某些 API,如 Fitbit,堅持在標頭中獲取數據。大多數人真的希望它在正文中,編碼為x-www-url-form-encoded,除了少數,例如 Notion,它更喜歡將它作為 JSON 獲取。
- 有些人希望你使用基本身份驗證來驗證此請求。許多人對此并不在意。但要小心,他們明天可能會改變主意。
我應該在哪里重定向我的用戶進行授權?
- Shopify 和 Zendesk 有一個模型,在該模型中,每個用戶都會獲得一個子域,例如 {subdomain}.myshopify.com。是的,它包括 OAuth 授權頁面,因此你最好將動態 URL 構建到你的模型和前端代碼中。
- Zoho Books 為不同地點的客戶提供不同的數據中心。希望他們記住他們的數據所在的位置:要授權你的應用程序,你的美國客戶應該訪問 https://accounts.zoho.com,歐洲人可以訪問 https://accounts.zoho.eu,歡迎印度人訪問 https://賬戶.zoho.in. 清單還在繼續。
但至少我可以選擇我的回調 URL,不是嗎?
如果你輸入http://localhost:3003/callback作為 Slack API 的回調,他們會友善地提醒你“請使用 https 確保安全”。是的,也適用于本地主機。幸運的是,在 localhost 上有 OAuth 重定向的解決方案。
我們可以繼續討論很長時間,但我們認為你現在可能明白了。
- OAuth 太復雜;讓我們制作一個更簡單的 OAuth 版本,它擁有我們需要的一切!XKCD
4、許多 API 向 OAuth 添加非標準擴展
盡管 OAuth 標準非常龐大,但許多 API 似乎仍然在其中尋找所需功能的差距。我們看到的一個常見問題是,除了access_token之外,你還需要一些數據才能使用 API。如果這些額外的數據可以與 OAuth 流中的 access_token 一起返回給你,那不是很好嗎?
我們實際上認為這是一個好主意——或者至少比強迫用戶在之后執行古怪的額外 API 請求來獲取此信息(看看你,Jira)要好。但這確實意味著你特別需要為每個 API 實現更多非標準行為。
以下是我們見過的一小部分非標準擴展:
- Quickbooks 使用realmID,你需要在每個 API 請求中傳入它。他們唯一一次告訴你這個realmID是作為 OAuth 回調中的附加參數。最好將它存放在安全的地方!
- Braintree 對companyID做同樣的事情
- Salesforce 為每個客戶使用不同的 API 基本 URL;他們稱之為instance_url。值得慶幸的是,他們在令牌響應中返回了用戶的instance_url和訪問令牌,但你確實需要從那里解析并存儲它。
- 不幸的是,Salesforce 還做了更煩人的事情:訪問令牌在預設時間后過期,這可以由用戶自定義。到目前為止還不錯,但出于某種原因,當你剛收到的訪問令牌將過期時,他們不會在令牌響應中告訴你(其他人都會這樣做)。相反,你需要查詢額外的令牌詳細信息端點以獲取令牌的(當前)到期日期。為什么,Salesforce,為什么?
- Slack 有兩種不同類型的范圍:你作為 Slack 機器人擁有的范圍和允許你代表授權你的應用程序的用戶采取行動的范圍。很聰明,但他們并沒有為每個范圍添加不同的范圍,而是實現了一個單獨的user_scopes參數,你需要在授權調用中傳遞該參數。你最好了解這一點,祝你好運,在你的 OAuth 庫中找到對此的支持。
為了簡潔起見,我們將跳過我們遇到的許多非真正標準的 OAuth 流程。
5、“invalid_request”調試 OAuth 流程很困難
調試分布式系統總是很困難。當你使用的服務使用廣泛的、通用的錯誤消息時,這會變得更加困難。
OAuth2 有標準化的錯誤消息,但它們在告訴你正在發生的事情方面與上面標題中的示例一樣有用(順便說一下,這是 OAuth 標準推薦的錯誤消息之一)。
你可能會爭辯說 OAuth 是一個標準并且每個 API 都有文檔,那么有什么可以調試的呢?
很多。我無法告訴你文檔出錯的頻率。或者缺少一個細節。或者還沒有更新最新的變化。或者當你第一次看到它們時你錯過了什么。我們實施的 80% 的 OAuth 流程在首次實施時都存在一些問題,需要調試。
在我調試 OAuth 流程時 Randall 是如何觀察我的?XKCD
某些流程也會因看似隨機的原因而中斷:例如,如果你傳入 PKCE 參數,LinkedIn OAuth 就會中斷。你得到的錯誤?“客戶端錯誤 - 無效的 OAuth 請求。” 那是……告訴?我們花了一個小時才明白傳遞(可選,通常被忽略)PKCE 參數是中斷流程的原因。
另一個常見錯誤是發送的范圍與你在應用程序中預注冊的范圍不匹配。(預注冊范圍?是的,現在很多 API 都需要這樣做。)這通常會導致出現有關范圍存在問題的一般錯誤消息。呃。
6、在 API 之上構建的繁瑣審批
事實是,如果你通過使用他們的 API 構建其他系統,你可能處于較弱的位置。你的客戶要求集成,因為他們已經在使用其他系統。現在你需要讓他們開心。
公平地說,許多 API 都是自由的,并為開發人員提供簡單的自助服務注冊流程來注冊他們的應用程序并開始使用 OAuth。但是一些最流行的 API 在你的應用程序公開并且可以被任何用戶使用之前需要進行審查。同樣,公平地說,大多數審核過程都很正常,可以在幾天內完成。就最終用戶的安全性和質量而言,它們可能是凈收益。
但一些臭名昭著的例子可能需要數月才能完成,有些甚至需要你簽訂收入分成協議:
- 如果你想訪問包含更敏感的用戶數據(例如電子郵件內容)的范圍,Google 需要進行“安全審查”。我們聽說這些審核可能需要數天或數周才能通過,并且你需要付出大量的工作。
- 想要與 Rippling 集成?準備好接受他們的 30 多個問題和安全預生產篩選。我們聽說訪問需要幾個月的時間(如果你獲得批準)。
- HubSpot、Notion、Atlassian、Shopify 以及幾乎所有其他擁有集成市場或應用程序商店的人都需要經過審查才能在其中列出。有些評論很溫和,有些則要求你提供演示登錄、視頻演練、博客文章(是的!)等等。但是,在市場或商店中列出通常是可選的。
- Ramp、Brex、Twitter 和許多其他公司沒有面向開發人員的自助注冊流程,需要你填寫表格以進行手動訪問。許多人很快就能處理請求,但我們仍在等待數周后的回復。
- Xero 是貨幣化 API 的一個特別極端的例子:如果你想超過 25 個連接帳戶的限制,你必須成為 Xero 合作伙伴并將你的應用程序列在他們的應用程序商店中。然后,他們將從該商店產生的每條線索中(截至撰寫本文時)收取 15% 的收入。
7、OAuth 安全性很棘手并且是一個動態變化的目標
隨著攻擊被發現,可用的 Web 技術不斷發展,OAuth 標準也發生了變化。如果你希望實施當前的安全最佳實踐,OAuth 工作組為你提供了一份相當冗長的指南。如果你使用的 API 目前仍在使用 OAuth 1.0a,你就會意識到向后兼容性是一場永無止境的斗爭。
幸運的是,安全性隨著每次迭代而變得更好,但這通常是以開發人員的更多工作為代價的。即將推出的 OAuth 2.1 標準將使一些當前的最佳實踐成為強制性的,包括強制性的 PKCE(目前只有少數 API 需要這個)和對刷新令牌的額外限制。
至少 OAuth 已經實現了雙因素身份驗證模型。XKCD
隨著訪問令牌的過期和刷新令牌的興起,可能已經迎來了最大的變化。從表面上看,這個過程似乎很簡單:每當訪問令牌過期時,用刷新令牌刷新它并存儲新的訪問令牌和刷新令牌。
實際上,當我們實現這個時,我們必須考慮:
- 競爭條件:我們如何確保在刷新當前訪問令牌時沒有其他請求運行?
- 如果你在一定天數內未使用刷新令牌(或者如果用戶已撤銷訪問權限),某些 API 也會使刷新令牌過期。預計一些刷新會失敗。
- 某些 API 會在每次刷新請求時向你發出一個新的刷新令牌……
- 但有些人也默默地假設你會保留舊的刷新令牌并繼續使用它。
- 一些 API 會以絕對值告訴你訪問令牌過期時間。其他人只是相對“從現在開始的幾秒鐘”。還有一些,例如 Salesforce,不會輕易泄露此類信息。
8、最后:還沒有談到的事情
遺憾的是,我們只是觸及了 OAuth 實施的皮毛而已。現在,OAuth 流程已經運行并且獲得了訪問令牌,這時候需要考慮:
- 如何安全地存儲這些訪問令牌和刷新令牌。它們就像你用戶帳戶的密碼。但是散列不是一種選擇;你需要安全、可逆的加密。
- 檢查授予的范圍是否與請求的范圍匹配(某些 API 允許用戶更改他們在授權流程中授予的范圍)。
- 刷新令牌時避免競爭條件。
- 在提供商端,檢測用戶撤銷的訪問令牌。
- 讓用戶知道訪問令牌已過期,以便他們可以在需要時重新授權你的應用程序。
- 如何撤銷你不再需要的訪問令牌(或用戶要求你根據 GDPR 刪除的訪問令牌)。
- 可用 OAuth 范圍的更改、提供程序錯誤、缺少文檔等。
原文鏈接:https://www.nango.dev/blog/why-is-oauth-still-hard