【編者按】這篇文章介紹了 OAuth 的實踐中的問題,如:OAuth 標準過于龐大和復雜、每個人的 OAuth 都有細微的不同、許多 API 在 OAuth 中添加了非標準的擴展、 調試 OAuth 很難、在 API 之上構建應用需要繁瑣的審批、OAuth 存在安全性問題等。作者構建的一個開源服務 Nango,它旨在簡化 OAuth 的流程和提高安全性,適用于多種 API,來解決這些問題。
鏈接:https://www.nango.dev/blog/why-is-oauth-still-hard
作者 | Robin Guldener譯者 | 明明如月
責編 | 夏萌
出品 | CSDN(ID:CSDNnews)
我們為 50 個最受歡迎的 API 實現了 OAuth。結果:一言難盡。
圖源:nango.dev
OAuth 是一個標準協議,它支持 OAuth 2.0 的客戶端庫已經存在,幾乎適用于你能想象到的所有編程語言。你可能因此會得出結論,有了客戶端庫,你應該能在大約 10 分鐘或至少一小時內實現任何 API 的 OAuth。然而,理想很豐滿,現實很骨感。很難想象一個人能夠在 10 分鐘或一小時內實現任何 API 的 OAuth。
實踐中的 OAuth
我們針對 50 種最受歡迎的 API 使用了OAuth,如 google (GmAIl、日歷、表格等),HubSpot,Shopify,Salesforce,Stripe,Jira,Slack,Microsoft (Azure, Outlook, OneDrive),LinkedIn,Facebook 和其他使用OAuth 的 APIs。
我們的結論:現在 OAuth 的體驗和 2008 年的 JAVA 的瀏覽器 API 差不多。雖然大家普遍認同事情應該如何做,但實際上每個 API 都有自己對標準的解讀,實現上的差異和特殊性,以及非標準行為和擴展。結果是:每個細節都有可能出現問題和錯誤。
到底問題出現在哪里,讓我們深入研究一下!
問題1:OAuth 標準過于龐大和復雜
"這個 API 也使用 OAuth 2.0,我們幾周前已經做過了。我明天就能搞定。"——實習生的最后一句話
OAuth 是一個龐大的標準。它由官方網站上的 17 個 RFC(定義標準的文檔)共同構成。它們涵蓋了從 OAuth 框架和 Bearer 令牌到威脅模型和私鑰 JWT 的所有內容。
你可能會問:“所有這些 RFC 都和一個簡單的第三方訪問令牌授權 API 有關嗎?”你說得對。讓我們只關注那些可能與典型的 API 第三方訪問用例相關的東西:
- OAuth 標準:OAuth 2.0 現在是默認的,但一些 API 仍然使用 OAuth 1.0a(而且 2.1 就要來了)。一旦你知道你的 API 使用的是哪個,繼續下一步:
- 授權類型:你需要 authorization_code , client_credentials ,還是 device_code ?它們分別是做什么的,你應該在什么時候使用它們?如果有疑問,就試試 authorization_code 。
- 順便說一下:刷新令牌也是一種授權類型,但它不是用來獲取訪問令牌,而是用來延長訪問令牌的有效期。它們的工作方式是標準化的,但你如何首先請求它們卻不是。稍后再說。
- 現在你已經準備好了你的請求,讓我們看看官方 OAuth 參數;它們有 72 個,每個都有明確的含義和行為。你可以在 這里 查看它們。常見的例子有 prompt , scope , audience , resource , assertion ,和 login_hint 。然而,在我們的經驗中,大多數 API 提供者似乎和你現在一樣對這個列表一無所知,所以不用太擔心。
如果你覺得這還是太復雜了,需要學習很多東西,我們傾向于同意你的看法。大多數構建公共 API 的團隊似乎也同意這一點。他們沒有遵循完整的 OAuth 2.0 標準,而只是根據他們的 API 用例選擇性地實現了一些 OAuth 功能。這導致了文檔中很長的頁面,概述了 OAuth 對于這個特定 API 是如何工作的。但我們很難責怪他們;他們只是想要提供一個好的開發者體驗(developer experience,DX),讓他們的 API 更容易使用和理解,而不是遵循復雜的標準。他們可能認為 OAuth 2.0 標準太復雜或不適合他們的場景,所以他們只選擇了一些他們認為有用的功能。雖然這可能是出于好心,但也可能導致混亂和不一致。
Salesforce 的authorization_code OAuth流程。這個簡單的10步過程的清晰視圖有什么不好呢?
問題是,每個人對 OAuth 的理解都有不同的想法,所以你最終得到了很多不同的(子)實現。
問題 2:每個人的 OAuth 都有細微的不同
每個 API 都實現了不同的 OAuth 子集,這迫使你仔細閱讀他們冗長的 OAuth 文檔:
1、他們在授權調用中需要哪些參數?
- 對于 Jira,你必須設置 audience 參數來指定你要訪問的 Jira 實例的 URL。Google 傾向于通過不同的 scope 來處理這個問題,但是非常關心 prompt 參數。與此同時,Microsoft 的某個人發現了 response_mode 參數,并要求你總是將它設置為 query 。
- Notion API 采取了一種激進的方法,摒棄了無處不在的 scope 參數。事實上,你甚至在他們的 API 文檔中找不到“scope”這個詞。Notion 稱它們為capabilities,并且你在注冊應用時設置它們。我們花了 30 分鐘的困惑時間才明白發生了什么。他們為什么要重新發明這個輪子?
- offline_access 的情況更糟:現在大多數 API 都會在一段時間后讓訪問令牌過期。要獲得刷新令牌,你需要請求“offline_access”,這需要通過一個參數、一個 scope 或者你在注冊 OAuth 應用時設置的東西來完成。詢問你的 API 或 OAuth 醫生以獲取詳細信息。
2、他們希望在令牌請求調用中看到什么?
- 一些 API,比如 Fitbit,堅持要在請求頭中獲取數據。大多數人真的希望它在正文中,編碼為 x-www-url-form-encoded ,除了少數幾個,比如 Notion,它們更喜歡以 JSON 的形式獲取。
- 有些人希望你用 Basic auth 來驗證這個請求。許多人不在乎這個。但要小心,他們可能明天就改變主意了。
3、我應該把我的用戶重定向到哪里去授權?
- Shopify 和 Zendesk 有一種模式,每個用戶都有一個子域名,比如 {subdomain}.myshopify.com。是的,這也包括 OAuth 授權頁面,所以你最好在你的模型和前端代碼中構建動態 URL。
- Zoho Books 為不同地區的客戶提供了不同的數據中心。希望他們記得他們的數據在哪里:要授權你的應用,你的美國客戶應該訪問 https://accounts.zoho.com ,歐洲客戶可以訪問 https://accounts.zoho.eu ,印度客戶歡迎訪問 https://accounts.zoho.in 。列表還在繼續。
4、但至少我可以選擇我的回調 URL,對吧?
我們可以繼續說很久,但我們想你現在應該明白了。
- 如果你輸入 http://localhost:3003/callback 作為 Slack API 的回調,他們會友好地提醒你“出于安全考慮,請使用 https”。是的,也適用于 localhost。幸運的是 有解決方案可以在 localhost 上進行 OAuth 重定向。
OAuth 太復雜了;讓我們做一個更簡單的 OAuth 版本,它有我們需要的一切!©XKCD
問題 3:許多 API 在 OAuth 中添加了非標準的擴展
盡管 OAuth 標準很全面,但許多 API 仍然發現它有一些功能缺失。我們遇到的一個常見問題是,除了access_token 之外,你還需要一些數據才能與 API 交互。這些額外的數據如果可以在 OAuth 流程中與access_token一起返回給你,會更方便。
但這確實意味著更多的非標準行為,你需要為每個 API 特別實現。
下面是我們看到的一些非標準擴展的列表:
- Quickbooks 使用了一個 realmID ,你需要在每個 API 請求中傳遞它。他們唯一告訴你這個 realmID 的時候是作為 OAuth 回調中的一個額外參數。最好把它存放在某個安全的地方!
- Braintree 做了同樣的事情,用了一個 companyID
- Salesforce 對于每個客戶使用了不同的 API 基礎 URL;他們稱之為 instance_url 。謝天謝地,他們在令牌響應中與訪問令牌一起返回了用戶的 instance_url ,但你確實需要從那里解析出它并存儲它。
- 不幸的是,Salesforce 還做了更讓人惱火的事情:訪問令牌在預設的一段時間后過期,這可以由用戶自定義。到目前為止還好,但出于某種原因,他們在令牌響應中沒有告訴你你剛剛收到的訪問令牌何時會過期(其他人都做了這件事)。相反,你需要查詢一個額外的令牌詳情端點來獲取令牌的(當前)過期日期。為什么呢,Salesforce,為什么?
- Slack 有兩種不同類型的 scope:作為 Slack 機器人持有的 scope 和允許你代表授權你應用的用戶采取行動的 scope。聰明,但是他們沒有只是為每個 scope 添加不同的 scope,而是實現了一個單獨的 user_scopes 參數,你需要在授權調用中傳遞它。你最好注意這一點,并祝你好運找到對此支持的 OAuth 庫。
為了簡潔和簡單起見,我們跳過了我們遇到的許多不太標準的 OAuth 流程。
問題 4:“invalid_request” —— 調試 OAuth 很難
調試分布式系統本就不易,如果服務返回的是廣泛的、通用的錯誤消息,就更加困難了。
OAuth2 有標準化的錯誤消息,但它們在告訴你發生了什么方面,和標題中的例子一樣有用(順便說一下,這是 OAuth 標準推薦的錯誤消息之一)。
你可能會認為 OAuth 是一個標準,每個 API 都有文檔,所以不需要調試。很多。我無法告訴你文檔有多少次是錯的?;蛘呷鄙偌毠??;蛘邲]有更新最新的變化。或者你第一次看它們時錯過了什么。我們實現的大約 80% 的 OAuth 流程在第一次實現時都有一些問題,需要調試。
Randall 似乎能看穿我調試 OAuth 流程的心情?©XKCD
有些流程也會因為看似隨機的原因而中斷:例如,LinkedIn OAuth 會在你傳入 PKCE(Proof Key for Code Exchange)參數時中斷。你得到的錯誤是什么?“client error - invalid OAuth request。”這是……有說服力的嗎?我們花了一個小時才明白傳入(可選的,通常被忽略的)PKCE 參數是什么導致了流程中斷。另一個常見的錯誤是發送與你預先注冊的應用不匹配的 scope。(預先注冊 scope?是的,現在很多 API 都要求這樣做。)這通常會導致一個關于 scope 有問題的通用錯誤消息。真是令人沮喪。
問題 5:在 API 之上構建應用需要繁瑣的審批
事實是,如果你要利用第三方的 API 來為其他平臺或服務構建應用,你可能處于弱勢的位置。你的客戶要求集成,是因為他們已經在使用其他系統了。現在你需要讓他們滿意。
客觀地說,許多 API 都很靈活,提供了方便的自助注冊流程,讓開發者可以注冊他們的應用并開始使用 OAuth。但是一些最受歡迎的 API 需要在你的應用變成公開并且可以被他們的任何用戶使用之前進行審核。再次公平地說,大多數審核過程都是合理的,可以在幾天內完成。它們可能對于最終用戶的安全和質量有凈增益。
但但是一些出了名的例子可能需要花費幾個月才能完成,有些甚至要求你進入收入分成協議:
- 如果你想訪問包含更敏感用戶數據的 scope,比如電子郵件內容,Google 需要一個“安全審核”。我們聽說這些審核可能需要幾天或幾周才能通過,并且需要你在自己這邊做不少工作。
- 想要與 Rippling 集成?準備好回答他們的 30 多個問題和安全預生產篩選吧。我們聽說獲得訪問權限需要幾個月(如果你被批準的話)。
- HubSpot、Notion、Atlassian、Shopify 和幾乎所有有集成市場或應用商店的人都需要審核才能在那里上架。有些審核很溫和,有些則要求你提供演示登錄、視頻演示、博客文章(是的?。┑鹊?。不過,在市場或商店上架通常是可選的。
- Ramp、Brex、Twitter 和相當多的其他服務沒有為開發者提供自助注冊流程,而是要求你填寫表單以獲得手動訪問權限。許多人很快就會處理請求,但我們仍然在等待一些人在幾周后回復。
- Xero 是一個特別極端的例子,它是一個收費的 API:如果你想超過 25 個連接賬戶的限制,你必須 成為 Xero 合作伙伴 并將你的應用列在他們的應用商店中。他們會從每個從那個商店生成的潛在客戶中拿走(截至本文撰寫時)15% 的收入分成。
問題 6:OAuth 存在安全性問題
隨著 OAuth 的安全漏洞被發現,以及網絡技術的進步,OAuth 標準也不斷更新和完善。如果你想要實現當前的安全最佳實踐,OAuth 工作組有一個詳細的指南供你參考。如果你正在與一個仍然使用 OAuth 1.0a 的 API 合作,你就會意識到向后兼容性是一個持續的挑戰。
幸運的是,每一次迭代,安全性都在變得更好,但這通常是以開發者更多工作為代價的。即將到來的 OAuth 2.1 標準將使一些當前的最佳實踐成為強制性的,并包括強制性的 PKCE(今天只有少數 API 需要這個)和刷新令牌的額外限制。
至少 OAuth 已經實現了一個雙因素認證模型。©XKCD
最大的變化可能是由過期的訪問令牌和刷新令牌的引入引發的。理論上,這個過程看起來很簡單:每當一個訪問令牌過期時,用刷新令牌刷新它,并存儲新的訪問令牌和刷新令牌。實際上,當我們實現這個功能時,我們必須考慮:
- 競爭條件:我們如何確保在我們刷新當前訪問令牌時沒有其他請求運行?
- 一些 API 也會在你一定天數內不使用它們(或者用戶已經撤銷了訪問權限)時讓刷新令牌過期。預計一些刷新會失敗。
- 一些 API 在每次刷新請求時都會給你一個新的刷新令牌……
- ……但有些則默默地假設你會保留舊的刷新令牌并繼續使用它。
- 一些 API 會以絕對值告訴你訪問令牌的過期時間。其他人只以相對的“從現在開始的秒數”來表示。還有一些,比如 Salesforce,不輕易透露這種信息。
最后:一些我們還沒有談到的事情
不幸的是,我們只是觸及了你的 OAuth 實現的表面問題?,F在你的 OAuth 流程運行起來了,你得到了訪問令牌,是時候考慮以下問題了:
- 如何安全地存儲這些訪問令牌和刷新令牌。它們就像你用戶賬戶的密碼一樣。但是單向加密不是一個選項;你需要安全的、可逆的加密。
- 檢查授予的 scope 是否與請求的 scope 匹配(有些 API 允許用戶在授權流程中更改他們授予的 scope)。
- 避免刷新令牌時出現多個請求同時修改同一個令牌的情況(也稱為競爭條件)。
- 檢測用戶在提供者端撤銷的訪問令牌。
- 通知用戶訪問令牌過期,并引導他們重新授權你的應用。
- 如何撤銷你不再需要的訪問令牌(或者用戶根據 GDPR 要求你刪除的訪問令牌)。
- 你還需要應對可用 OAuth scope 的變化、提供者 bug、缺失的文檔等等問題。
有更好的方法嗎?
如果你讀到這里,你可能會想,“一定有更好的方法!”
我們認為有,這就是為什么我們正在構建 Nango:一個開源、自包含的服務,它提供了預先構建好的 OAuth 流程、安全的令牌存儲和自動令牌刷新適用于 90 多個 OAuth API。
如果你試一試,我們很樂意聽到你的反饋。如果你想和我們分享你最糟糕的 OAuth 恐怖故事,我們也很樂意在我們的 Slack 社區中聽到。
OAuth 的實踐過程中,除了本文提到的問題外,還有哪些問題?