Sign-in with OOO ID

最近工作上在研究 Sign-in with Apple/Google ID,其實這件事情也作過很多次了,不過距離上次也有點久了,不知道那些 SDK 有沒有什麼改變,所以還是要重頭來,而且這次多研究了一個 Apple,有搞懂些以前沒釐清的細節,想說紀錄一下。

首先就是,我發現到 Google 和 Apple 現在都會回一個 ID Token,內容則是 JWT,而且是認真的 JWT,真的會有帶一點使用者資料(像是 email)的,可以用來作 user authentication(認證)用的,這在以前只有 OAuth 2.0 的時候是沒有的,查一陣子資料才發現這其實是 OpenID Connect(簡稱 OIDC) 規範的,該規範是是 2014 年發布的,其實還比 JWT 早定稿。

相較於 OAuth 2.0 做的是 authorization(授權),OIDC 做的則是 authentication(認證),只不過 OIDC 是整個依附在 OAuth 2.0 的架構上,利用的就是 OAuth 2.0 的 response_type,本來只有codetoken兩種,在 OIDC 多提供了 id_token,ID Token 是用 JWT,而且也有明確定義有裡面的 user profile 相關的欄位(不是定義哪些是必須提供,而是定義好語意和對應的欄位名稱)。

第二個則是我一直存疑已久的問題,就是 OAuth 2.0 的設計上基本是用 redirection flow,而且 redirect_uri 也是必要的,但是 Google 其實一直都支援純 JS 的方案,也就是說 Relying Party(應用程式端)不需要實作一個 redirection endpoint 來接資料,前端的 JS 就可以直接拿到 authorization code 和 access token,這次也認真的研究了這個問題,結果發現,Apple 的 OAuth 流程中,response_mode 給的值是目前不在標準內的web_message,而 Google 則是 redirect_uri 給的是一個沒見過的 scheme:storagerelay://的 URI。

其實我一開始以為這些設計,是各個公司自己想一想然後就自己做下去的,結果沒想到其實不是,首先是 Apple 的實作,用的是 OAuth 2.0 Web Message Response Mode 這份草稿的設計,這份草稿其中一位作者 Nat Sakimura 現在是 OpenID 的 chairman,而且他也是其他很多相關規範的作者(像是 JWT),不過這份草稿只有一版且沒人更新,有很多細節沒有寫定,大部分都是提供範例程式碼而已,所以 Apple 有些東西不是照範例做,例如 message 的 payload;Google 實作的則是 OAuth 2.0 IDP-IFrame-based Implicit Flow,這份文件其實是我最後才找到的,因為這份只有出現在 OpenID 的 mailing-list,而且也完全沒有討論和更新,不像前一份還有發到 IETF,回到文件內容,這份的作者,就全部都是 Google 的人了,內容完整許多,也有定義好 message 的 payload 結構,不過還是有些細節有缺,像是認證完成後的回傳事件,現在 Google 用的事件名稱在這份文件中就找不到。

除了這兩份草稿外,其實還有一份是 Curity 提出的 OAuth 2.0 Assisted Token,一樣有發到 IETF,而且還比較有在更新,最新一版是 2021 的,Assisted Token 的設計是多給了一個流程叫做 Assisted Token Flow,用不一樣的 URL 來使用這個 flow,這個流程的最後一步就是 postMessage 回到 opener/parent,規範寫的比較完整,包括各種新屬性的 registry、message 的 payload,還有安全性相關的考量都有,不過我看一看有些地方覺得有些疑惑,第一個是 message payload 的結構似乎辨識度沒很高,第二個則是完全沒提到 refresh token 和 authorization code,不確定是目前還沒考慮到那邊,還是因為安全性考量把它們先排除。

第三個是 Apple 的實作,雖然基本上還是照著 OAuth,但是其實限制更多,首先就是,scope就只有 email 和 name 可以拿,而且 Apple 做成,只有第一次 authorization 時,會把 user profile 傳給 RP,之後是拿不到的(不過 email 有在 id token 內),然後,雖然可以拿到 access token,但是其實完全沒有可以使用它的 API endpoint,所以什麼事情都不能作,真的只能用來做 authentication,最後一個,就是用 code 換 token 時,要給的client_secret參數還特別麻煩,要自己組一個 JWT(但是也因此會 expire 比較安全),然後用之前從 Apple Developer 後台生好的 secret key 去算 signature。

最後一個想紀錄的就是,這幾份標準和文件裡面幾乎都有一個章節是 Security Consideration,內容雖然不全面但是還蠻值得一看的,也有提到一些攻擊手法,而除此之外,其實還有一份文件:RFC6819: OAuth 2.0 Threat Model and Security Considerations,內容就是有說明 OAuth 2.0 各種設計在安全性上的考量(像是為什麼有 Refresh Token,什麼情境下它有助於安全性的提升),以及各種可能的攻擊手法。


HTTP 103 Early Hints

前幾天晚上前同事陶百貼了個 Tweet,說到 Chrome 要移除 HTTP/2 Server Push 了:

仔細看一下,發現原來大家用 Server Push 都還是為了提升網頁第一屏的速度,但是 Server Push 一直有一些難解的問題,像是不知道 client 端有沒有 cache,實作和支援比較麻煩,而 Chrome 要移除 Server Push 前,其實先實作了 RFC-8279 的 HTTP 103: Early Hints,為的就要讓 Server Push 現在作的事情先有替代方案。

Early Hints 應該算是 Fastly 提出的,RFC 文件作者是 Kazuho Oku,實際上應該也有其他 Fastly 的人參與構思和試驗,支援 Early Hints 的環境下,一個 HTTP request 看起來就像是下面這樣:

Client request:

  GET / HTTP/1.1
  Host: example.com


Server response:

  HTTP/1.1 103 Early Hints
  Link: </style.css>; rel=preload; as=style
  Link: </script.js>; rel=preload; as=script

  HTTP/1.1 200 OK
  Date: Fri, 26 May 2017 10:02:11 GMT
  Content-Length: 1234
  Content-Type: text/html; charset=utf-8
  Link: </style.css>; rel=preload; as=style
  Link: </script.js>; rel=preload; as=script

  <!doctype html>
  [... rest of the response body is omitted from the example ...]

很特別的,就是在於有兩段 response,第一段就是 103 的 status code,然後內容就是 Link headers 了,接著才是常見的 200 回應,看到這邊,自然的出現第一個問題:現有的瀏覽器能相容嗎?

這個問題在 Stack Overflow 也有人問,結果回答在 RFC 文件內其實就有,只不過是放在第三章的安全性那邊,我一開始也因為先跳過這章而沒發現,總之關於這個問題,就是如果是 HTTP/2 的話,就比較沒問題,HTTP/1.1 的話,理論上應該要可以相容(沒功能但是也不出錯),但是無法保證現在有在用的 HTTP/1.1 client 都有正確的處理 1xx response,所以比較建議是 HTTP/2 才回 103。

過了兩天後,我更仔細的研究一下,發現其實早在 HTTP/1.1 時,就有把 1xx 的處理需求定義好了:

A client MUST be able to parse one or more 1xx responses received prior to a final response, even if the client does not expect one. A user agent MAY ignore unexpected 1xx responses.

就是說早在 HTTP/1.1 時的設計,就允許 1xx 接 200 的回應,而且還應該要支援多個 1xx 回應,而最後的那個 200(其實是 2xx 到 5xx 都可以),則是稱為 final response,至於這處理的方式,在 WHATWG 的 fetch 的 4.7 章則有清楚的寫下流程,在該章節的第九項裡面的第五子項目,寫成程式碼大概長成:

while (true) {
  const response = await networkTransmit();
  const status = response.statusCode;
    
  if (status >= 100 && status <= 199) {
    // handle 1xx response
    continue;
  } else {
    break;
  }
}

// handle final response

所以理論上,Early Hints 的設計在正確支援 HTTP/1.1 但是還沒有支援 Early Hints 的瀏覽器就應該要可以正常的略過,而不會把它當成是 final response。

解決完第一個問題後,接著來仔細的看看剛剛範例的 server response:

HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script

HTTP/1.1 200 OK
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script

<!doctype html>
[... rest of the response body is omitted from the example ...]

不知道會不會有人疑惑,為什麼不直接用 200 response 裡面回應的 Link header 就好了?其實我一開始也是這樣想,不過這完全是因為這個問題落入身為前端工程師的我的盲點之中,因為現在前端開發主流是 SPA,通常 HTTP server 回的就是一個靜態的 HTML 檔案,所以回應速度超快。不過,如果回應的 HTML 文件,是由程式語言動態生成的,或許還需要查詢一下資料庫之類的,那這個回應時間就會變慢了,而 HTTP 103 Early Hints 就是在這種狀態下用的,在你的 server 端程式開始處理 request 之前,就先丟 103 的 status code 和 Early Hints 的內容回給瀏覽器,然後才接著處理資料和生成 HTML 文件,這種情境下,Early Hints 就顯得比較有差異了。Nitropack 的文章就解釋的很清楚,還有附上詳細的說明圖。

相較於 Server Push,其實 Early Hints 的設計簡單很多,所有的傳輸還是從 client 端看有沒有 cache ,決定要不要發 request,而這種操作已經非常成熟(相較於 server push),相信很多地方可以直接使用現有的程式碼來實作,最大的隱憂,就只是不相容 HTTP/1.0,然後會擔心有 HTTP/1.1 的 client 端沒正確實作吧,畢竟 1xx 的處理機制雖然早早就設計好,但是實際上 1xx 有被廣泛使用也是這幾年的事。

目前 Chrome 是從 103 開始支援 Early Hints 的,並且預計在 106 正式移除 Server Push,至於其他瀏覽器則是都還沒有支援, Firefox 是有計畫要支援,進度有點緩慢就是。

最後,Fastly 其實有提供一個測試用的網站:https://early-hints.fastlylabs.com/,不過這個網站不是用來測試你的瀏覽器支不支援 Early Hints 的,而是用來測試先 103 然後接 200 的 response 會不會有非預期的問題(也就是相容性的測試),如果想要直接看看來回的內容,也可以直接用 curl:

curl -v https://early-hints.fastlylabs.com

Shopify App

之前開發 Shopify App 時,為了搞定他的安裝搞了蠻久,所以決定來紀錄一下踩到的坑,這篇文章適合已經開始在開發 Shopify App 的人閱讀,有些 Shopify App 的基本知識就不會提到,以下內文幾個名詞先定義清楚一下:

  • App 指的是我們開發的 Shopify 第三方 app
  • Merchant 指的是在 Shopify 上開店的商家
  • 安裝 app 指的是 merchant 在他們的 Shopify 商店上安裝我們開發的第三方 app

首先就是,我踩的很多坑有一部分原因是因為我用 NodeJS 作為 server 端的語言,選的是 Express,但是官方的 Express 架構的 app 範例已經停止維護了,取而代之的,是 Koa 版本的 @shopify/koa-shopify-auth,只有負責驗證相關的 middleware,不過其實我也就剛好是需要 auth 相關的部分,只是差在不是 Express 版,我也還可以研究看看要怎樣自己實作了。

大概看一下,發現其實還有另外一個 @shopify/shopify-api 是底層負責處理跟 Shopify 相關的邏輯,所以理論上我也可以使用它來搭配 Express,不過這裡首先就有一個坑了,初始化的範例是長這樣:

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SHOPIFY_APP_SCOPES,
  HOST_NAME: process.env.SHOPIFY_APP_URL.replace(/^https:\/\//, ''),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // More information at https://github.com/Shopify/shopify-node-api/blob/main/docs/issues.md#notes-on-session-handling
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

可以看到,最後有一個SESSION_STORAGE,這是個處理 merchant 在安裝 app 時,我們的 app 拿到的 access token 的儲存方式的 adapter,不過官方的範例是用 Memory Storage,這個 adpater 是只有存在記憶體內,其實只適用於開發用,只要你的 server 一重開,所有的 merchant 就都要重新安裝你的 app,不然你的 app 會沒有 access token 跟 Shopify 溝通,實際上你應該要參考 Custom Session Storage 這份文件,挑選適用的 adapter,我則是參考範例寫了一個 GCP FireStore 的版本,當然另外沒特別提到的就是,因為是儲存 access token,最好要考慮一下 DB 的加密。

第二個坑,就是要怎樣做 Shopify 的 authentication 以及 identification,先來說如何驗證 request 是可信的,在 Shopify API 的設計,就是要靠 query string parameter 裡面的 hmac,他是根據你的 App 的 secret 來計算出來的,然後,這裡的坑就是官方套件@shopify/shopify-api內有個validateHmac可以用,但是它的計算其實是不正確的,它是用白名單只有取部分的 query string parameter 來計算,結果和 Shopify 給的就會有出入,所以我是參考 GitHub issue 討論串內 Muhammad Kamal 給的範例來使用。

第三個坑,則是安裝 App 用的 route 了,Shopify 的設計有點特別,所有的初始 request (不論是第一次安裝、還是從 Shopify 後台進入 App 的設定畫面),都長的很接近,所以你就要根據各種狀況來決定該做什麼事情,以下是所有可能的狀況:

  • 第一次來安裝
  • 安裝後進到設定畫面
  • 曾經安裝過,但是需要重新授權,可能的原因:
    • App 需要的權限有變動
    • App 端的 access token 失效了
  • Shopify 認為已經安裝了,但是 app 端沒資料

扣除需要的權限有變動之外,其實就是排列組合,Shopify 端認為有沒有安裝過,和 App 端認為有沒有安裝過,二乘二共四種可能性,不過實際上只有三種處理方式:初次安裝、重新授權、安裝沒問題的快樂路線(happy path)整理成程式流程大概是:

  1. 驗證 hmac,沒過可以直接回 400
  2. 判斷 shop 是否有在資料庫中
  3. 2 有的話驗證資料庫中的 access token
  4. 3 驗證通過的話,狀態就是 happy path,Shopify 認為 app 有裝,app 端檢查也沒問題,我把這狀態命名為valid
  5. 3 驗證沒通過的話,判斷有沒有session這個 query string 參數
  6. 5 有的話,狀態就是 app 端的 access token 不能用了,需要走重新授權的流程,我把這狀態命名為invalid
  7. 5 沒有的話,就是第一次安裝的流程,我把這狀態命名為not_found
  8. 最後就是 2 沒有的話也是走初次安裝的授權流程,同樣也可以叫not_found

然後 app 需要的權限變動的話,理論上是每次進來,驗證 access token 的時候,可以去打 API 問目前 token 的 access scope,不過這部份我沒實做,因為目前我還沒有相關需求。

網路上可能可以找到X-Shopify-API-Request-Failure-Reauthorize這個 header,不過這個其實不是 Shopify API 的回應,而是 Shopify 的 app-template 裡面設計的機制,它們的 app template 裡面,server 端在轉發 Ajax API request 時,如果收到 Shopify 端的錯誤後,就加上這個 header 回給 app 前端,app 前端收到這個 header 後就可以透過 Shopify app-bridge 進入重新授權的流程。

講到這邊,或許有人會好奇,為什麼需要把安裝 app 和重新授權兩個流程的處理方式分開?其實這可以算是第四個坑,也是和使用者體驗有關係,狀況就是,Shopify 認為是初次安裝時,是直接進入 OAuth 的流程,所以是瀏覽器的最上層視窗直接轉址到 auth 頁面,但是如果是需要重新授權的情形,則是 Shopify 端認為已經安裝好,但是 app 這邊認為需要重新跑一次 OAuth,而這時候,連到 app server 的瀏覽器視窗是在 Shopify 商店後台的 iframe 內,在 iframe 內也無法正確的完成 OAuth 授權,所以需要用 Shopify 現在一套叫 app-bridge 的工具幫忙,讓 OAuth 流程從最上層視窗開始,所以需要回一個 HTML 頁面,引入 app-bridge 的 script,然後執行以下的的 JS:

const AppBridge = window['app-bridge'];
const createApp = AppBridge.default;
const Redirect = AppBridge.actions.Redirect;
const app = createApp({
	apiKey: '{{API_KEY}}',
	host: '{{HOST}}',
});
const redirect = Redirect.create(app);

redirect.dispatch(
	Redirect.Action.REMOTE,
	'/url/to/your/auth?shop={{SHOP}}'
);

當然記得要把該替換的東西替換上去,然後就可以看到正確的從最上層視窗開始進入 OAuth 授權的流程了。

最後一個坑,其實就是 merchant 反安裝 app 後,Shopify 和 app 端的狀態就會不一致的問題,Shopify 端認為沒安裝,但是 app 端認為有安裝,雖然我上面設計的程式流程已經可以處理這種狀況(驗證 access token 會失敗,然後沒有session參數,所以會進入初次安裝),但是這種情形還是應該要能避免就避免,而解法就是要支援 webhook,要作的事情就是:

  1. 安裝完成的 callback 去訂閱APP_UNINSTALLED這個 webhook event
  2. 然後在收到這個事件後,把資料庫中的對應資料刪除

這邊我是用@shopify/shopify-api提供的工具像是Shopify.Webhooks.Registry.registerShopify.Utils.deleteOfflineSession,真的想要自己作也不是辦不到,不過我記得 Shopify 的 webhook 處理起來有點麻煩。

這些細節就是官方文件沒有好好寫清楚,雖然官方文件內容已經很多,有努力整理了,但是實際上要自己接就還是遇到了不少問題,所以特別寫一篇文章紀錄,雖然不知道會不會有其他中文圈的人需要自己來做 Shopify app 就是了,可以直接用他們的 app template 還是比較簡單啦。


Safari 3rd-Party Cookie

Apple ITP

Apple 之前有宣告要完全阻擋 3rd-party cookie,iThome 也有相關的報導,iOS 和 iPadOS 應該是已經上線了,然後最近 Mac 版 Safari 也快要上線了,所以這篇來記錄一下要怎樣因應還有一些參考資料。

其實真的會寫到第三方 cookie 的服務是沒想像多的,如果不是開發給其它網站用的第三方服務的話(不是掛 script 而已),那其實沒那麼常見,舉例來說:很多人可能會覺得 Google Analytic 會受影響,但是其實並沒有,一般網站掛 Google Analytic 算是掛上 3rd-party script,但是它寫的 cookie 是 1st party cookie,也就是寫在你的網站的 domain 下,Google 的文件也有很詳細的說明他的每個 cookie 的用途,然後仔細看就會找到還有寫如何跨網域追蹤,而這其實是需要帶一些參數過去的,如果 GA 是用 3rd-party cookie 寫在 Google 自己的 domain 的話,要跨網域追蹤就不需要這樣帶參數了,我是覺得 Apple 的 ITP 比較是針對廣告和 Facebook,早幾年前 Facebook 可以用 like button 來簡單的做到跨站追蹤,現在那些 iframe 都會被認為是 3rd-party,cookie 會和 1st-party 放不同區(partition),甚至本來如果有先去看過 facebook.com 之後,會有 24 小時可以存取該網域 3rd-party cookie 的能力也在 ITP 2.0 移除,facebook 後來加上了fbclid這個參數來追蹤連出去的連結,然後 ITP 2.2 就又針對這種連結裝飾(link decoration)也設了 cookie 的存取限制(剛好同時也影響到 Google Analytic)。

如果真的是需要作為 3rd-party 端提供服務的話怎麼辦呢?其實一開始 Apple 那篇文章,有列了幾個方案,其中正規的兩個方案:

  1. 用 OAuth 2.0 作為 user auth 的方案,然後第一方網站拿到 token 後自己存好(作為 1st-party cookie 或是其它儲存方法)。
  2. Storage Access API,這是 Apple 所提出的 Web API,在被視為第三方的 context 中(例如 iframe),可以透過 Storage Access API 來取得 1st-party cookie 的存取權限,不過一般人直接用這個 API 要權限,可能會覺得奇怪怎麼 Safari 都沒有問使用者要不要給,權限就拿到了,其實這是因為 Apple 那邊的想法是這個 API 要盡可能的不干擾使用者,所以只有被歸類(classified)為有追蹤能力的域名才會跳出視窗跟使用者詢問,至於這個歸類的方法是在 ITP 1.0 中提出的,Apple 考慮到隱私問題,所以這個機制是用機器學習的,每台電腦/裝置都維護自己的清單,沒有中心化的黑名單(Firefox 應該是用這種方法),而如果想要親自驗證自己的 domain 要是被歸類為追蹤網站的話,會發生什麼事的話,也有篇文章介紹,我自己有測試過也確實看到了那個詢問視窗。

然後如果要用 Storage Access API,其實還有些限制,Safari 從 1.0 開始,就有個針對 3rd-party cookie 的限制,就是使用者要曾經直接訪問過該網域,並且寫入過 1st-party cookie,之後該網域才能對 3rd-party cookie 做存取,而這項限制也延伸到 Storage Access API 這邊,一樣要先作為 1st-party 寫入過 cookie,之後才能夠透過 Storage Access API 取得 1st-party cookie 的存取權限,Apple 負責 ITP 的 John Wilander 最近正在寫相關的文件,裡面就有提到,然後這個限制 Firefox 也有,不過 Firefox 似乎是 30 天內有訪問過該網域就可以。

寫到這邊,其實有件事情忘記先提,就是網路上你去搜尋 Safari 3rd Party Cookie 會找到一些方法說可以成功讀寫 3rd-party cookie 的,那些全部都已經失效了,而且不只是 cookie,所有可以寫入的東西像是 DOM Storage 也是有受到一樣的限制保護的(然後 Storage Access API 現在只能拿到 cookie 的權限),目前也沒有出現其它的繞過方式,而且就算有人找到,Apple 都會修掉的,所以如果有這需求還是趕快用 Storage Access API 實做吧(別忘了 feature detection)。

然後或許有人會覺得 ITP 沒檔到 Google Analytic 好像沒什麼意義,其實 John Wilander 早在 2017 年就有在 WebAppSec 稍微提過 Single Trust 這件事,提的就是網頁內掛的 3rd-party script 其實是安全性隱憂,應該只有同 domain 的東西可以信任,在 cookie 這邊來說就是 3rd-party script 不應該有存取網站 1st-party cookie 的權限(不過後來發生的是某航空公司的信用卡資訊輸入頁面放的第三方 script 會做 key log),如果真的進行,這個改變可以想像的到影響非常的巨大,舉例來說,以前的 Performance Practice 其中一項是把 static file 放到 CDN 並且用不同 domain host,但是這樣其實就會被當成是 3rd-party script 了,雖然他在我們的認知下是可信任的,然後目前也有非常大量的現存網站是這樣做。目前 Apple 也有在做一些相關的研究,其中一個已經廣為人知(?)的就是 Safari 現在有在紀錄 3rd-party script 的數量,另外就是我之前在 SameSite Cookie 這篇文章有提到的,Mike West 起草的 First-Party Sets,透過/.well-known/下的檔案定義可以被認為是 1st-party 的 domain 清單,假設未來真的要做到 single trust 的程度,要處理 CDN 之類的問題,像是 First-Party Sets 的機制就不可少。

最後附上一些延伸的參考資料:


Scroll to Text

Scroll To Text

Chrome 最近有個新功能叫做 Scroll to Text Fragment,雖然在 Chrome Platform Status 網站那邊寫 M80 會可以用,不過我實際上測試正式版的 Chrome 80 還沒有,但是 Chrome Canary 已經是預設啟用了。這個功能讓你可以在網址內的 Fragment 段(#後面那段),用新定義的語法來讓瀏覽器直接捲動到指定的文字位置,語法如下:

#:~:text=[prefix-,]textStart[,textEnd][,-suffix]
          context  |-------match-----|  context

如果有在用 Chrome Canary 的可以直接試試看這個連結。這個語法其實還蠻靈活的,可以和以前 id identifier 並用:

#targetID:~:text=theText

這樣如果找不到文字,瀏覽器還可以改用 targetID 定位;如果網頁內有多個一樣的文字,可以用prefix--suffix給出前後文來讓瀏覽器找到正確的目標;再來就是如果要標註的文字很長,也可以用textStart,textEnd來標註,這樣就不用在網址內放一大串文字了;然後也可以標註多段文字,用&切開,和給參數的格式一樣:

#:~:text=firstText&text=secondText

正式的文件現在是放在 WICG 那邊,GitHub 那邊的 Proposal repo 則是有一段解釋為什麼選擇用:~: 當分割符號的段落,我覺得這種脈絡的紀錄是很重要的,這邊簡單說一下,一開始有考慮過##這種比較容易想到形式,但是有些 URL parser 是由右到左的可能會有錯誤,再來他們列出一堆不太會有人去用的組合來當候選,然後去看 Google 那邊過去五年的紀錄,結果就是:~:最沒人用,只有 0.0000039% 的比例,所以目前是選擇這個分割符號。

我是蠻喜歡這新功能(標準?)的,Mozilla 也覺得還可以考慮看看,不過目前這個功能似乎還沒在 stable channel 啟用,應該是因為隱私問題,Chrome 負責的團隊也有整理相關的資訊,大概簡單說就是有可能透過頁面的讀取時間或是scrollTop的值來判斷你開啟的頁面內有沒有特定字串,然後就會有外洩的疑慮。另外還有一個讓人擔心的問題是這個功能可能會讓那些本來用 fragment 當成 route 的 SPA 壞掉,W3C TAG design review 那邊他們自己也有提到。

其實這個 scroll to text 的功能在之前還有一套提案,不過不是由瀏覽器開發商所提,而是 indieweb 提的,叫做 Fragmention,這組提案功能就比較陽春而且不成熟了:

#some%20text

重點在裡面那個%20,也就是空白字元,Freagmention 的提案是你的目標文字要有空白,因為 HTML 的 id 不能包含空白字元,所以如果有空白字元就表示不是要找 id,這個提案由我來看就是很明顯沒搞過 i18n 的啊~


W3C TAG

去年我在規劃 COSCUP 的 Open Web Technologies 的時候,有準備了一個備用的演講,題目是關於到哪裡可以追蹤到新的網路標準發展,其中的一個可以關注的資源,就是 W3C TAG(Technical Architecture Group)design-reviews,我一開始其實是誤打誤撞發現這個 issue list 的,有點像是發現寶庫一樣,幾乎所有的新標準都會到這邊提出審查請求,而除了標准之外,Web 相關的比較重要的修改提案也會發到這邊來,像是最近要進行的 SameSite=Lax by default,還有剛提出的 Partial freezing of the User-Agent string

W3C 有一份 Consortium Process Document 的文件(簡稱為 Process),內容包括了 W3C 的一些基本構成,規範如何建立 Working/Interest Group 以及這些小組如何發展技術報告(Technical Report),這邊說的技術報告包括了草稿到 W3C Rec(推薦標準),除此之外,這份文件還有制訂了兩個獨立組別的構成方法,這兩個組分別為:Advisory Board(顧問團) 和 Technical Architecture Group(TAG、技術架構組),兩者都有負責解決跨技術報告(aka 標準)之間的問題,前者是負責非技術的問題,後者則是負責技術問題。W3C TAG 的成員結構現在是:

  • Tim Bernes Lee 為終生成員
  • W3C 總監
  • 總監可以提名三位成員
  • 另外有六名成員則是由 Advisory Committee(諮詢委員會,其實就是所有 W3C 會員代表人)選出

其實看他們的審查意見都覺得真的很厲害,不過這些成員的名字其實曝光都很少。然後我一直很好奇,為什麼幾乎所有的技術報告都來這邊提審查,應該是有在技術報告開發流程上寫到才會這麼多,結果那份 Process 文件找來找去就是沒找到,花了幾個小時最後終於在其中一分外連的「如何建立一個工作/興趣小組」的文件中找到,其實找 TAG 做審查是 Horizontal Review(橫向審查) 的一部分,Horizontal Review 指的是技術報告在發展的過程中,找各個相關/相依的小組一起來做審查,而這些組別間的關係其實是寫在小組各自的章程裡面的,該份文件還有列出一些比較關鍵的組別:

這五個確實是非常跨技術的主題,尤其是 TAG,技術的議題都跑不掉,我也循線去其它四個小組看了一下,發現真的也有相關的審查請求,不過不同小組申請審查的管道不一樣,有的還是用 W3C 傳統的 mailing-list,TAG 已經用 GitHub 算是非常先進而且方便訂閱了,從開發者的角度如果要關心網路標準的新發展可以以這邊為主,明顯比較缺的就是 CSS 的新東西不會發到這邊,我自己是還有訂 CSS Working Group 的 issue list。

最後就是現在台灣的 W3C 會員似乎只剩下 台灣數位出版聯盟,日本現在倒是很多組織有加入了,像是不知道為什麼加入的 DWANGO,看起來也沒參加 Chinese Interest Group 的 Danmaku


DNS CAA record

過年前因為工作關係第一次注意到 CAA record (Certification Authority Authorization) 這個東西,簡單說明就是透過 DNS record 來設定你的 SSL cert 的簽發單位白名單,一開始的規範是 RFC6844,其實原理也不複雜,不過我就在遇到用 AWS ACM 簽發憑證時說檢查不過的狀況,有趣的是該 domain 沒有設定任何 CAA,搜尋研究一陣子後發現可能是因為該 domain 是 CNAME 去到別的第三方 domain 才會這樣,然後果然發現這個問題其實很久了。

不過其實原始版本的 RFC6844 其實沒有要求檢查 CNAME 目標的 CAA,而是在 2017 年的勘誤 5065 中才加入的,不過這個修改造成很多問題,所以 letsencrypt 在同一年就又換回舊的實做了。CAA record 看起來也因此放棄這個修改了,在用來取代 RFC6844 的 RFC8659 中,就完全沒有提到 CNAME 的檢查,甚至在與舊版相異的附錄也是特別提到這點差異,不過 RFC8659 還很新,是 2019 年 11 月的,看起來 AWS 還沒修正也情有可原(?)。


Public Suffix List

最近因為花了很多時間研究 Safari 和第三方 Cookie,常常看到一個專有名詞 eTLD+1,之前只知道和 domain name 及 TLD 相關,不清楚確切的定義,所以又去查了一下,結果找到解釋最清楚的竟然是 Go 的 publicsuffix 套件的說明文件,總之簡單比較不明確的解釋,eTLD 指的是 effective TLD,像是netnet.tw這類,域名註冊商可以提供的網址後綴,依此類推,eTLD+1 就是 eTLD 再多一段,也就是一般人可以註冊到的網域名稱,像是我這邊用的othree.net,至於部落格的子網域blog.othree.net就不是 eTLD+1 了。

其中的 eTLD 又稱為 Public Suffix,然後 Mozilla 有維護一份 Public Suffix List,給瀏覽器用的,主要用途就是避免寫入太高權限的 cookie,例如我要是把 cookie 寫到.net層的話,所有的.net域名的網站都會讀的到它,就會有安全性問題,這份清單現在已經是主要瀏覽器開發商都有在使用了,它的內容除了那些 eTLD 之外,其實還有私人公司提交的,像是 blogspot 列了一大串,還有 github 有列github.iogithubusercontent.comgithub.io是 GitHub Pages 的預設 domain,像我的 Github Page 就會用 othree.github.io,GitHub 提交這筆記錄,在現代瀏覽器就會限制我在othree.github.io不能寫 cookie 到github.io,這樣可以確保所有使用者的頁面不會互相影響。

我還順便亂瀏覽一下內容,發現 Amazon 手上好多的 gTLD,像是booksong,然後他們的cloudfront.net也有提交,還有一堆其它的 aws 網域名稱;另外就是 DynDNS 和 no-ip 兩個類似服務都提交超多的;然後還發現一間叫 nymnom.com 的域名註冊商,專門提供一堆nomnym結尾的域名,搞不清楚這兩個單字的意思啊。


Robots Exclusion Protocol

Google Webmaster Central Blog 昨天發表了 Formalizing the Robots Exclusion Protocol Specification 這篇文章,介紹到 Robots Exclusion Protocol (REP) 這個正在標準化的草案,REP 其實就是已經被廣泛使用的 robots.txt 檔案,robots.txt 誕生至今已經 25 年了,當初是由 Martijn Koster 所設計,早期網路的東西基本上就是先做,設計的不錯大家就跟著抄,不一定會有什麼標準的文件,robots.txt 就是這樣其實一直都沒正式的標準文件,我以前還真的有懷疑過怎麼找不到,直到 Google 這篇文章才確定了,真的一直以來是沒標準的,雖然 Google 衝網路標準太快讓人有不少意見,不過這次我倒是覺得樂觀其成,而且他們也還公開了他們的 robots.txt 的 parser matcher lib

消息來源


SameSite Cookie

Cookie Time

Cookie 的規格是 RFC 文件所定義的,其實一直以來都有在演化,目前為止已經有三個版本,照順序分別是 RFC2109RFC2965 和最新的 RFC6265,像是HttpOnly就是 RFC6265 才出現的,而最近最新的屬性,就是SameStie了,其實它和HttpOnly的起源很接近,都是近年來比較被人重視的安全性和隱私的原因,Google 的 web.dev 有一篇圖文並茂的文章介紹的很詳細- SameSite cookies explained,建議還不清楚什麼是 SameSite cookie 的可以先去看一下。

SameSite Cookie 的標準文件其實還未正式定稿,目前還算是草稿 RFC6265bis(bis 在 The Tao of IETF 有解釋),不過主流瀏覽器都已經支援了,然後其實這篇文章我想說的是最近在 W3C TAG 看到的 Issue 373:SameSite=Lax by default,是由 Google 的 Mike West 提案要把 SameSite 的預設值改為 Lax,現在 Google Chrome 已經有這個實驗選項了,而且除了 SameSite 預設值的改變之外,其實還有一個修改目標是SameSite要在Secure的時候才能設為None,這項改變相對而言是影響比較大的,所以提案的文件(Incrementally Better Cookies)也有提到可以分步進行,另外就是 Firefox 也表示有意願來實做,看起來至少 SameSite 預設改為 Lax 這件事應該是不會太久之後就會發生了。

在花時間看一些文件內的參考資料後,發現 Mike West 還有其它幾份相關的草案:

  • first-party-sets WICG/first-party-sets 是用/.well-known/URL 來跟客戶端溝通,可以提供 first party 的域名清單;
  • First-Party Sets and SameSite Cookies 利用上面的 first-party-set 資訊,然後提供兩種新的 SameSite 值:FirstPartyLaxFirstPartyStrict
  • HTTP State Tokens 定義了個標準化的 session token,是由瀏覽器端產生的 token,而不是 Web API,至於怎麼傳遞到 server 端,怎樣溝通有效期等都有寫在規範內,Incrementally Better Cookies 的想法也是從這份草案中的特性而來。

這些草案都還蠻有趣的,至於會不會定稿成為規範甚至大家都開始實做,目前就還很難斷定了。


此類別所有文章