2024年1月21日日曜日

OpenID Providerを作る)scopeの定義と返却する属性

こんにちは、富士榮です。

積み残した実装を徐々に進めていきましょう。

その前にこれまでのおさらいです。


今回はscopeです。
OpenID Connect coreの仕様の該当箇所ではscopeと対応する属性について、このように定義されています。(簡略化しています)
scope属性形式備考
profile
namestring
family_namestring
given_namestring
middle_namestring
nick_namestring
preffered_usernamestring
profilestringURL
picturestringURL
websitestringURL
genderstringfemale/maleを利用
birthdatestringISO 8601:2004形式(YYYY-MM-DD)
zoneinfostringIANAタイムゾーン形式(JapanやAmerica/Los_Angelesなど)
localestringBCP47言語タグ表現(ja_JP、en_USなど)
updated_atnumberunixtime
email
emailstring
email_verifiedboolean検証済みならtrue
address
formattedstring
JSON
street_addressstring
localitystring
regionstring
postal_codestring
countrystring
phone
phone_numberstringE.164形式
phone_number_verifiedboolean検証済みならtrue

なお、上記とは別にOpenID ProviderをOAuth2.0の認可サーバと兼ねる場合でOpenID Connectプロトコルを使う場合は「openid」scopeの指定が必須となります。


例によってユーザ情報はハードコードしていますが、こんな感じの処理になるはずです。
/utils/user.js
exports.getUserIdentity = function(scopes) {
// ユーザデータの定義
let userIdentity = {
local_identifier: "test",
// profile scope
name: "taro test",
given_name: "taro",
family_name: "test",
middle_name: "",
nickname: "",
preferred_username: "test@example.jp",
profile: "https://twitter.com/phr_eidentity",
picture: "https://1.gravatar.com/avatar/25eee85430bd0bbdcb9cff75655afa43cc9f69bc8730aec852d8538179646ef1",
website: "hhtps://idmlab.eidentity.jp",
gender: "male",
birthdate: "1900-01-01",
zoneinfo: "Japan",
locale: "jp_JP",
updated_at: 1704034800,
// email scope
email: "test@example.jp",
email_verified: true,
// address scope
address: {
formatted: "Kokyogaien, Chiyoda-ku, Tokyo 1000002 JAPAN",
street_address: "Kokyogaien",
locality: "Chiyoda-ku",
region: "Tokyo",
postal_code: "1000002",
country: "JP"
},
// phone scope
phone_number: "+81-3-1234-5678",
phone_number_verified: true
}
// スコープによって返却する属性の絞り込み
if(!scopes.includes("profile")){
delete userIdentity.name;
delete userIdentity.given_name;
delete userIdentity.family_name;
delete userIdentity.middle_name;
delete userIdentity.nickname;
delete userIdentity.preferred_username;
delete userIdentity.profile;
delete userIdentity.picture;
delete userIdentity.website;
delete userIdentity.gender;
delete userIdentity.birthdate;
delete userIdentity.zoneinfo;
delete userIdentity.locale;
delete userIdentity.updated_at
};
if(!scopes.includes("email")){
delete userIdentity.email;
delete userIdentity.email_verified;
};
if(!scopes.includes("address")){
delete userIdentity.address;
};
if(!scopes.includes("phone")){
delete userIdentity.phone_number;
delete userIdentity.phone_number_verified;
};
return userIdentity;
}

scopeによってユーザ情報から必要な値のみを返すようにしています。

また、前回のPairwise識別子の実装と合わせてid_tokenのペイロードを作る処理は上記をコールする形で以下のように実装しています。
/oauth2/oauth2.js
//
// scope関連の処理
//
// scopeの判断
const scopes = req.query.scope.split(" ");
// 本来はscopeにopenidが入っていない場合はエラーとする(仕様上はopenidが含まれない場合の動作は未定義)
// scopeに応じたユーザの情報を取得する
let payload = userIdentity.getUserIdentity(scopes);
// Pairwise識別子の生成
const PPID = utils.createPPID(payload.local_identifier, req.query.redirect_uri);
// ローカル識別子の削除
delete payload.local_identifier;
// PPIDをsubとして設定
payload.sub = PPID;

これで例えば、openid、address、phoneを指定するとこんな感じでid_tokenが帰ってきます。
http://localhost:3000/oauth2/authorize?scope=openid%20address%20phone&response_type=id_token&client_id=111&redirect_uri=https://jwt.ms&state=hoge


必要な属性だけがid_tokenに含まれていることがわかります。

また、今回対応するscopeと属性を増やしたのでメタデータ(/discovery.js)も更新しておきましょう。
const scopes = ["openid", "profile", "email", "address", "phone"];

const claims_openid = ["sub", "iss", "aud", "exp", "iat", "nonce", "c_hash", "at_hash"];
const claims_profile = ["name", "family_name", "given_name", "middle_name", "nick_name", "preffered_username", "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale", "updated_at"];
const claims_email = ["email", "email_verified"];
const claims_address = ["address"];
const claims_phone = ["phone_number", "phone_number_verified"];
const claims = claims_openid.concat(claims_profile, claims_email, claims_address, claims_phone);

今回のUpdateを含むコードはこちらにあげてありますので参考にしてください。


0 件のコメント: