この記事は、mediba Advent Calendar 2020 2日目です。
auスマートパスのバックエンドエンジニア(BE)、アーキテクト担当をしている北田です。
みんな大好きUser-Agent(以下UA)を通して今年を振り返り、来年を展望してみようと思います。
User-Agentは便利
auスマートパスでは、auブランドで提供している多彩な端末(以下デバイス)をサポートしています。
そのため、開発プロジェクトではデバイス要件を必ず確認するようにし、デバイス種別も定義していす。
これまでは、WebAppサーバにて、UAをキーにデバイス種別を判定し、HTMLテンプレートを変更する機能(デバイス種別判定機能)を実装していました。
社内ではよくあるパターンです。
auスマートパスは、2020年2月にUX向上を目的としてシステムの刷新を行い、BFFとREST APIサーバで構成されるようになりました。
これを機に、機能配置も見直し、デバイス種別判定機能を独立させました。
Lambda@Edgeでは、よくあるパターンかと思いますが、デバイス種別毎にオリジンを変更する機能を実装しました。
図を見て、富豪過ぎワロタと思われた方もいらっしゃるとは思いますが、その狙いや背景はまた後日...
デバイス種別判定機能を独立して開発、提供出来るようになったので、BFFと開発チームはそのリソースをUX向上に注力できるようなりました。
UAは、多彩な端末をサポートするauスマートパスには、便利で頼もしい存在です。
User-Agentはリスク
UAを頼りにデバイス毎に最適なUXの提供を図ってきたauスマートパスですが、リスクも明らかになってきました。
わがままボディ
各所で指摘されている通り、UAはわがままボディです。
以下は、2020年冬モデルの最新端末 Google Pixel 5に搭載されているChromeのUAです。
Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36
何度見ても何を伝えたいのかわからない、わがままボディぷりです。
iOS端末に搭載されているauスマートパスアプリは、もっとわがままです。
Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148/smps-app/9.1.1
最近ですと、我らがCloudFront様が素敵なカスタムヘッダ(CloudFront-Is-Android-Viewer
やCloudFront-Is-IOS-Viewer
)を用意してくれており、涙ぐましい正規表現もパースの結果で分岐するというアレな実装も必要がなくなりました。
とはいえ、OSバージョンの判定処理は、自前で組む必要があり、以前アレな実装を続けています。メンテナンス性は、高いとは言えません。
ゆるふわ
各所で指摘されている通り、UAはゆるふわです。
リクエストヘッダに付与されますので、クライアントからすると容易に偽装でき、サーバからすると容易に取得出来てしまいます。
クライアントサイドにおいても、フリーハンドで取得出来てしまいます。
console.log(navigator.userAgent)
# Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148/smps-app/9.1.10
デバイス種別という要件が存在し、その種別に応じたUIを提供しているauスマートパスでは、dev tool等での検証が容易であり、サーバサイドでの機能にUAを活かす事もできるメリットはあります。
しかし、リクエストヘッダの取得に何かしらの理由で失敗した場合、そのデバイス種別に対してUIを提供出来ないデメリットとリスクが存在します(2020年5月には、同様の理由で障害が発生してしまいました)。
また、以前から指摘されている通り、フィンガープリンティングに利用出来てしまい、プライバシー保護にも課題があります。
ゆるふわ故にウェブの理想から大きく逸脱した目的に用いられ、拡張され続けてきたとも言えそうです。
ウェブは使用しているブラウザーや機器に関係なく、誰からでもアクセスできるものです。
ユーザーエージェント文字列を用いたブラウザーの判定 - HTTP | MDN
User-Agentと未来の私
UAに依存するリスクは、各所の動向をフォローしても明らかです。
Googleは、UA文字列を凍結し、User Agent Client Hints(以下UA-CH)を提唱しています。
We want to freeze and unify (but not remove) the User Agent string in HTTP requests as well as in
navigator.userAgent
Intent to Deprecate and Freeze: The User-Agent string
コロナ禍で、2021年に延期のステータスの様です
Mozillaは、様子見のポジションです。
The proposal is to build on top of the Client Hints infrastructure and solve these problems over time.
User Agent Client Hints · Issue #202 · mozilla/standards-positions
auスマートパスは、UAの課題に対して、どう取り組むべきでしょうか。
UA-CHの動作確認サイトとその実装から、サーバサイドの観点で考察してみます。
尚、UA-CHの動作確認は、Chrome v85 stable以上のバージョンと関連するフラグを有効にする必要があります。
サーバサイドUA-CH
まずは、動作確認サイトへのリクエストとレスポンスを確認してみます。
初回のリクエストでは、以下のようなヘッダが付与されています。
リクエストヘッダは、sec-ch-ua
及びsec-ch-ua-mobile
のみ設定されており、制限されていることが伺えます。
また、server.jsでもレスポンスヘッダ:accept-ch
を付与する実装を確認出来ます。
res.set('Accept-CH', acceptCH.join(', '));
header = 'Accept-CH: ' + acceptCH.join(', ');
2回目のリクエストでは、以下のようなヘッダが付与されています。
レスポンスヘッダ:accept-ch
は、付与されておりません。
1回目のレスポンスにヘッダとして付与されていた、accept-ch
の値に基づいたリクエストヘッダが付与されています。
2回目のリクエスト処理の実装は以下の通り、リクエストヘッダを指定しているわけではなく、ブラウザがUA-CHの仕様に基づき付与していることが伺えます。
fetch('/show-headers.json' + document.location.search)
.then((res) => {
if (res.status === 200) {
res.json().then((data) => {
let secChText = '';
for (const header in data['Sec-CH']) {
secChText += `Sec-CH-${header}: ${data['Sec-CH'][header]}\n`;
}
document.querySelector('.request').textContent = secChText;
});
}
});
上記の挙動は、2020年12月現在のものであり、今後もアップデートが予想されます。
引き続き、フォローする必要がありそうです。
デバイス種別判定機能をUA-CHする
考察を踏まえ、デバイス種別判定機能は、どうあるべきでしょうか。
要件それ自体を見直す必要もありそうですが、UA-CHに適合していくのであれば、BFFは再びスマートになる必要があるのでしょうか。
JavaScript APIを用いたクライアントサイドでのUA-CHもあるので、時間をつくって、そちらも考察してみようと思います。
おわりに
UAは便利で息の長いウェブのレガシーですが、転換期にあります。
技術の境界線が変わろうとしていることを強く感じられるものでもあるので、引き続きフォローしていきたいと思います。
また、私はサーバサイドでの機能開発がメインのバックエンドエンジニアのポジションですが、auスマートパスでは、BFFの構築、運用にも携わっています。
技術の境界線を跨ぎ、ヒィヒィ言いながら貪欲に開発に取り組みたい仲間を募集していますので、興味が湧いた方、話だけでも聞いてみたい方は、是非エントリーをお願いします。