Qt OAuth2の概要
OAuth2
RFC 6749 OAuth 2.0は、パスワードのような機密性の高いユーザークレデンシャルを公開することなくリソースの認可を可能にする認可フレームワークを定義しています。
OAuth2 フレームワークでは、いくつかのクライアントタイプ(パブリックと機密)とフロー(暗黙的、認証コード、その他いくつか)が定義されています。典型的な Qt アプリケーションの場合、クライアントタイプはpublic ネイティブアプリケーションとみなされます。公開ということは、出荷されたバイナリに埋め込まれたパスワードのような秘密を保持するために、アプリケーションが信頼されていないことを意味します。
RFC 8252 OAuth 2.0 for Native Appsは、このようなアプリケーションのベストプラクティスをさらに定義しています。特に、Authorization Code Flowを推奨フローとして定義しているため、QtNetworkAuth はこのフローの具体的な実装を提供しています。
Qt 6.9 以降、QtNetworkAuth はRFC 8628 OAuth 2.0 Device Authorization Grant のサポートも提供しています。このデバイスフローは、入力機能が制限されていたり、ユーザーエージェントやブラウザを使用することが現実的でない、接続されたデバイスを対象としています。そのようなデバイスの例としては、テレビ、メディアコンソール、機械のHMI、IoTデバイスなどがあります。
次の表は、QtNetworkAuth モジュールがサポートする 2 つの具体的な OAuth2 フローの主な側面を示している:
側面 | 認証コードフロー | デバイス認証フロー |
---|---|---|
ネットワーク接続 | ネットワーク接続 | あり |
ユーザー・インタラクション | 同じデバイス上のブラウザ/ユーザーエージェント | 異なるデバイス上のブラウザ/ユーザーエージェント |
リダイレクト処理が必要 | はい | なし |
デバイス上の入力機能 | 豊富な入力機能 | 限られた入力機能または入力機能なし |
対象 | デスクトップ・モバイルアプリ | テレビ、コンソール、HMI、IoTデバイス |
OAuth2 では、一般的にブラウザであるユーザーエージェントを使用する必要があります。詳細はQt OAuth2 Browser Support を参照してください。
Qt OAuth2 クラス
QtNetworkAuth Qt OAuth2 には、具象クラスと抽象クラスの両方が用意されています。抽象クラスはカスタムフローを実装するためのもので、具象クラスは具体的な実装を提供します。
QtNetworkAuth には OAuth2 フローを実装するための抽象クラスが 2 つあります:
- OAuth2 フロー実装クラスはメイン API を提供し、フローのオーケストレーターとなる。抽象クラスはQAbstractOAuth2 で、具象実装はQOAuth2AuthorizationCodeFlow とQOAuth2DeviceAuthorizationFlow です。
- リプライハンドラ認証サーバからのリダイレクトとリプライを処理するクラス。リプライハンドラの抽象クラスはQAbstractOAuthReplyHandler であり、具象クラスはQOAuthHttpServerReplyHandler とQOAuthUriSchemeReplyHandler である。リプライハンドラの主な違いは、どのようなリダイレクトを処理するように設計 されているかということである。QOAuth2AuthorizationCodeFlow は、リダイレクトを処理するためにリプライハンドラを持つことに依存しているが、QOAuth2DeviceAuthorizationFlow は、リダイレクトに基づいていないので、リプライハンドラは使用しない。
認証コードの流れ
認可コードフローは、Qt アプリケーションのようなネイティブアプリケーションに推奨される OAuth2 フローです。
次のコードスニペットは、セットアップの例を示しています:
QOAuth2AuthorizationCodeFlow m_oauth; QOAuthUriSchemeReplyHandler m_handler; m_oauth.setAuthorizationUrl(QUrl(authorizationUrl)); m_oauth.setTokenUrl(QUrl(accessTokenUrl)); m_oauth.setClientIdentifier(clientIdentifier); m_oauth.setRequestedScopeTokens({scope}); connect(&m_oauth, &QAbstractOAuth::authorizeWithBrowser, this, &QDesktopServices::openUrl); connect(&m_oauth, &QAbstractOAuth::granted, this, [this]() { // Here we use QNetworkRequestFactory to store the access token m_api.setBearerToken(m_oauth.token().toLatin1()); m_handler.close(); }); m_handler.setRedirectUrl(QUrl{"com.example.myqtapp://oauth2redirect"_L1}); m_oauth.setReplyHandler(&m_handler); // Initiate the authorization if (m_handler.listen()) { m_oauth.grant(); }
ステージ
認可コードフローには2つの主要なステージがあります:リソースの認可(必要なユーザー認証を含む)とアクセストークンのリクエストです。さらに、アクセストークンの使用とアクセストークンの更新が続きます。次の図は、これらのステージを示しています:
- 認可段階では、ユーザーは認証され、リソースへのアクセスを認可します。これには、ユーザーによるブラウザの操作が必要です。
- 認可後、受け取った認可コードを使用してアクセストークンを要求し、オプションでリフレッシュトークンを要求します。
- アクセストークンが取得されると、アプリケーションはそれを使用して目的のリソースにアクセスします。アクセストークンはリソースリクエストに含まれ、トークンの有効性を確認するのはリソースサーバーです。トークンをリクエストに含める方法はいくつかありますが、HTTP
Authorization
ヘッダーに含めるのが最も一般的です。 - アクセストークンの更新。アクセストークンの有効期限は、通常1時間など比較的早く切れます。もしアプリケーションがアクセストークンに加えてリフレッシュトークンを受け取った場合、そのリフレッシュトークンを使って新しいアクセストークンをリクエストすることができます。リフレッシュ・トークンは長寿命であり、アプリケーションはそれを永続化することで、新たな認可段階(ひいてはブラウザとの再インタラクション)の必要性を回避することができます。
詳細とカスタマイズ
OAuth2 のフローは動的で、詳細を追うのは最初は難しいかもしれません。下図は、成功した認証コードフローの主な詳細を示しています。
わかりやすくするために、あまり使用されないシグナルを省略していますが、詳細と主なカスタマイズポイントを示しています。カスタマイズ・ポイントとは、アプリケーションがキャッチできる(そしてコールできる)さまざまなシグナル/スロットと、QAbstractOAuth::setModifyParametersFunction ()とQAbstractOAuth2::setNetworkRequestModifier ()で設定可能なコールバックのことである。
リプライ・ハンドラの選択
どのリプライハンドラを使うか、あるいは実装するかは、使用するredirect_uriに依存します。redirect_uri
は、認可段階が終了したときにブラウザがリダイレクトされる場所です。
ネイティブアプリケーションのコンテキストでは、RFC8252は3つの主要な タイプのURIスキームを概説している:loopback
https
と private-useである。
- 私用URI:OSがアプリケーションにカスタムURIスキームの登録を許可している場合に使用できる。このようなカスタムスキームを持つURLを開こうとすると、関連する ネイティブアプリケーションが開かれる。QOAuthUriSchemeReplyHandler を参照のこと。
- HTTPS URI:OS がアプリケーションにカスタム HTTPS URL の登録を許可している場合に使用できます。このURLを開こうとすると、関連するネイティブアプリケーショ ンが開かれる。OSがサポートしていれば、このスキームを推奨する。QOAuthUriSchemeReplyHandler を参照のこと。
- ループバックインターフェース:これらはデスクトップ・アプリケーションや開発中のアプリケーションによく使われる。QOAuthHttpServerReplyHandler は、リダイレクトを処理するローカルサーバーをセットアップする ことで、これらのURIを処理するように設計されている。
選択は以下のようないくつかの要因に依存する:
- 認可サーバーベンダーがサポートするリダイレクトURI。リダイレクトURIは認証サーバベンダによってサポートされる。サポートはベンダ によって異なり、特定のクライアントのタイプやオペレーティングシステムに特化し ていることが多い。また、アプリケーションが公開されているかどうかによってもサポートが異なる場合がある。
- ターゲットプラットフォームがサポートするリダイレクトURIスキーム。
- アプリケーション固有のユーザビリティ、セキュリティ、その他の要件。
RFC 8252では、他の方法よりもセキュリティとユーザビリティに優れている
https
スキームの使用を推奨している。
デバイス認証フロー
デバイス認証フローは、入力機能が制限されている接続デバイス、またはユーザーエージェント/ブラウザの使用が実用的でない接続デバイスを対象としている。
次のコード・スニペットは、セットアップの例を示しています:
m_deviceFlow.setAuthorizationUrl(QUrl(authorizationUrl)); m_deviceFlow.setTokenUrl(QUrl(accessTokenUrl)); m_deviceFlow.setRequestedScopeTokens({scope}); m_deviceFlow.setClientIdentifier(clientIdentifier);// クライアントシークレットの必要性は認可サーバーに依存するm_deviceFlow.setClientIdentifierShield({scope}); // クライアントシークレットの必要性は認可サーバーに依存するsetClientIdentifierSharedKey(clientSecret); connect(&m_deviceFlow, &QOAuth2DeviceAuthorizationFlow::authorizeWithUserCode, this,[](constQUrl&verificationUrl, constQString&userCode, constQUrl&completeVerificationUrl) {if(completeVerificationUrl.isValid()) {// 認証サーバが、 URLパラメータの一部として // 必要なデータを既に含む 完全なURLを提供した場合 、 // そのURLを使用することを選択できる。 qDebug() << "Complete verification uri:" << completeVerificationUrl; }else{// 認証サーバーは検証URLのみを提供したので、それを使用する。 qDebug() << "Verification uri and usercode:" << verificationUrl << userCode; } } ); connect(&m_deviceFlow, &::granted,[this]() { // ここではQNetworkRequestFactoryを使用してアクセストークンを格納する。QAbstractOAuth::granted, this, [this](){// ここではQNetworkRequestFactoryを使用してアクセストークンを格納していますm_api.setBearerToken(m_deviceFlow.token().toLatin1()); }); m_deviceFlow.grant();
ステージ
デバイス認証フローには、認証の初期化、トークンのポーリング、認証の完了という3つの主要ステージがある。これらに続いて、トークンの使用とトークンの更新が行われる。以下の図に、これらのステージを示す:
- 認可は、認可サーバーにHTTPリクエストを送信することで初期化される。認証サーバは、ユーザ・コード、検証URL、およびデバイス・コードをレスポンスとして提供する。
- 認可が初期化された後、ユーザは認可を完了するためのユーザコードと検証 URL を提供される。ユーザーへの情報提供はユースケースに依存し、画面上の可視URL、QRコード、電子メールなどがある。
- ユーザが認証を完了するのを待つ間、デバイス・フローは認証サーバにトークンをポーリングする。前のステップで受信したデバイス・コードが、認証セッションのマッチングに使用される。ポーリング間隔は認可サーバーによって決定され、通常は5秒である。
- ユーザが認可を受諾(または拒否)すると、認可サーバは要求されたトークンまたはエラーコード(拒否の場合)をポーリング要求に応答し、認可は完了する。
詳細とカスタマイズ
次の図は、フローをより詳細に示している。この図には、場合によっては必要となる主なカスタマイズポイントも示されている(たとえば、独自のパラメータや追加の認証クレデンシャルなど)。
トークンの更新
完全な OAuth2 フローでは、ユーザーとのインタラクションが必要です。これらのインタラクションを最小化するために、トークンはユーザの視点から静かにリフレッシュすることができます。
トークンをリフレッシュするには、認証中に認証サーバがリフレッシュトークンを提供する必要があります。リフレッシュトークンを提供するかどうかは認可サーバ次第であり、常に提供するサーバもあれば、全く提供しないサーバもあり、認可リクエストに特定のscope が存在する場合に提供するサーバもある。
次の図は、トークン・リフレッシュの詳細を示している:
上の図に示すように、トークンをリフレッシュする際には、通常のカスタマイズポイントも利用可能です。
アプリケーションの起動後にトークンをリフレッシュするには、アプリケーションはリフレッシュ・トークンを安全に永続化し、QAbstractOAuth2::setRefreshToken ()で設定する必要があります。QAbstractOAuth2::refreshTokens()は、新しいトークンを要求するために呼び出すことができます。
QAbstractOAuth2::accessTokenAboutToExpire QAbstractOAuth2::autoRefresh QAbstractOAuth2::refreshLeadTimeQt 6.9 以降、アプリケーションはリフレッシュ・コンビニエンス機能を使用して、自動的にトークンをリフレッシュすることもできます。
リフレッシュ・トークンの有効期限は、一般的に認証サーバからは提示されません (サーバのドキュメントは別として)。リフレッシュ・トークンの有効期限は、数日から数ヶ月、あるいはそれ以上と幅があります。さらに、他のトークンと同様に、リフレッシュ・トークンはユーザによって取り消される可能性があるため、いつでも無効にすることができます。したがって、QAbstractOAuth::requestFailed() やQAbstractOAuth2::serverReportedErrorOccurred() を使用して、リフレッシュの失敗を適切に検出することが重要です。
Qt OpenID Connectのサポート
OpenID Connect (OIDC)はOAuth2プロトコルの上のシンプルなIDレイヤです。認可がユーザーにアクションを実行することを許可する手段を提供するのに対し、OIDC はユーザーの信頼されたアイデンティティを確立することを可能にします。
Qt の OIDC サポートは、今のところID トークンの取得に限定されています。ID token
はJSON Web Token (JWT)で、認証イベントに関する主張を含んでいます。
特に、ID token
の検証やID token
の復号化は、現在のところ実装されていません。
アプリケーションが受信したトークンを検証できると仮定すると、トークンは(OIDCプロバイダ自体が信頼できる程度まで)ユーザーの身元を確実に確立するために使用できる。
ID トークンは機密情報であり、秘密にしておくべきである。ID トークンは、API 呼び出しで送信するためのものではありません - アクセストークンはそのためのものです。ベンダーの中には、アクセストークンに同じJWT形式を使用する場合があるが、これは実際のIDトークンと混同してはならない。IDトークンの場合、トークンを受け取るクライアントがトークンを検証する責任を負いますが、アクセストークンの場合、検証の責任を負うのはトークンを受け取るリソースサーバーです。
IDトークンの取得
IDトークンの取得は、アクセストークンの取得とよく似ています。まず、適切なスコープを設定する必要があります。Authorization Serverベンダーは、profile
やemail
などの追加スコープ指定子をサポートしている場合がありますが、すべてのOIDCリクエストにはopenid
スコープを含める必要があります:
m_oauth.setRequestedScopeTokens({"openid"});
OIDCでは、nonce パラメータを使うことを強く推奨する。これは、適切なNonceMode が設定されていることを確認することで行われる。
// This is for illustrative purposes, 'Automatic' is the default mode m_oauth.setNonceMode(QAbstractOAuth2::NonceMode::Automatic);
最後のステップとして、QAbstractOAuth2::granted シグナルまたはQAbstractOAuth2::idTokenChanged を直接リッスンすることができる:
connect(&m_oauth, &QAbstractOAuth2::idTokenChanged, this, [this](const QString &token) { Q_UNUSED(token); // Handle token });
IDトークンの検証
受信したIDトークンを検証することは、フローの重要な部分であり、完全に実装されると、やや複雑なタスクとなる。
バリデーションの概要は、以下のステップで構成されます。
- 必要に応じてトークンを復号化する(JWEを参照)。
- トークンのヘッダー、ペイロード、署名を抽出する。
- 署名の検証
- ペイロードのフィールドの検証(
aud, iss, exp, nonce, iat
など)
Qtは現在、IDトークンの検証をサポートしていませんが、jwt-cppなどの C++ライブラリがあります。
IDトークン検証の例
このセクションでは、jwt-cppライブラリを使用した簡単な検証を説明します。前提条件として、開発環境にOpenSSLライブラリがあり、アプリケーション・プロジェクトのソース・ディレクトリの下にjwt-cppインクルード・フォルダがある必要があります。
アプリケーション・プロジェクトのCMakeLists.txt
で、まず前提条件が満たされていることを確認します:
find_package(OpenSSL 1.0.0 QUIET) set(JWT_CPP_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include") if(OPENSSL_FOUND AND EXISTS "${JWT_CPP_INCLUDE_DIR}/jwt-cpp/jwt.h")
次に、必要なインクルードとライブラリを追加します:
target_include_directories(networkauth_oauth_snippets PRIVATE "${JWT_CPP_INCLUDE_DIR}") target_link_libraries(networkauth_oauth_snippets PRIVATE OpenSSL::SSL OpenSSL::Crypto) target_compile_definitions(networkauth_oauth_snippets PRIVATE JWT_CPP_AVAILABLE)
アプリケーションのソース・ファイルに、検証ライブラリをインクルードします:
#ifdef JWT_CPP_AVAILABLE #include "jwt-cpp/jwt.h" #endif
アプリケーションがID token
を受け取ったら、次はそれを検証する番だ。まず、JSON Web Key Sets(JWKS、OpenID Connect Discoveryを参照)から一致するキーを見つける。
try { const auto jwt = jwt::decode(m_oauth.idToken().toStdString()); const auto jwks = jwt::parse_jwks(m_jwks->toJson(QJsonDocument::Compact).toStdString()); const auto jwk = jwks.get_jwk(jwt.get_key_id());
そして、実際の検証を行う:
// ここでは、キーを導き出すために モジュラスと 指数を使用する。 qWarning() << "Modulus or exponent empty"; return false; }if(jwt.get_algorithm()!= "RS256") {// この例では、RS256のみをサポートしています。 qWarning() << "Unsupported algorithm:" << jwt.get_algorithm(); return false; }if(jwk.get_jwk_claim("kty").as_string()!= "RSA") {. qWarning() << "Unsupported key type:" << jwk.get_jwk_claim("kty").as_string(); return false; }if(jwk.has_jwk_claim("use")&&jwk.get_jwk_claim("use").as_string()!= "sig") {. qWarning() << "Key not for signature" << jwk.get_jwk_claim("use").as_string(); return false; }// シンプルな最小限の検証 (特殊なケースや 'sub' などの検証は省略)// jwt-cpp は 'exp', 'iat', 'nbf' が存在する場合はそれらもチェックする。const autokeyPEM=jwt::helper::create_public_key_from_rsa_components(n,e);autoverifier=jwt::verify().allow_algorithm(jwt::algorithm::rs256(keyPEM)) .with_claim("nonce",jwt::claim(m_oauth.nonce().toStdString())) .with_issuer(m_oidcConfig->value("issuer"_L1).toString().toStdString()) .with_audience(std::string(clientIdentifier.data())) .leeway(60UL); verifier.verify(jwt);qDebug() << "ID Token verified successfully"; return true; }catch(conststd::exception&e) {// エラーを処理する。あるいは、jwt-cpp呼び出しにエラーパラメータを渡す。qWarning() << "ID Token verification failed" << e.what(); return false; }
使用するライブラリの最新のドキュメントやサンプルを確認し、IDトークンの検証にも慣れておくことをお勧めする。
IDトークンの値の読み取り
IDトークンはJSON Web Token (JWT)形式で、ドット{'.'}で区切られたヘッダ、ペイロード、シグネチャ部分から構成されます。
IDトークンの値の読み取りは簡単です。例として、struct:
struct IDToken { QJsonObject header; QJsonObject payload; QByteArray signature; };
そして関数:
std::optional<IDToken> parseIDToken(const QString &token) const;
トークンを抽出することができる:
if (token.isEmpty()) return std::nullopt; QList<QByteArray> parts = token.toLatin1().split('.'); if (parts.size() != 3) return std::nullopt; QJsonParseError parsing; QJsonDocument header = QJsonDocument::fromJson( QByteArray::fromBase64(parts.at(0), QByteArray::Base64UrlEncoding), &parsing); if (parsing.error != QJsonParseError::NoError || !header.isObject()) return std::nullopt; QJsonDocument payload = QJsonDocument::fromJson( QByteArray::fromBase64(parts.at(1), QByteArray::Base64UrlEncoding), &parsing); if (parsing.error != QJsonParseError::NoError || !payload.isObject()) return std::nullopt; QByteArray signature = QByteArray::fromBase64(parts.at(2), QByteArray::Base64UrlEncoding); return IDToken{header.object(), payload.object(), signature};
さらに稀なケースでは、トークンがJSON Web Encryption (JWE)で暗号化され、内部的にJWTトークンを含んでいる場合があります。この場合、トークンを最初に復号化する必要があります。
OpenID Connectディスカバリー
OpenID Connect Discoveryは、OpenIDプロバイダと対話するために必要なOpenIDプロバイダの詳細を発見する手段を定義しています。これにはauthorization_endpoint
やtoken_endpoint
URL などが含まれる。
これらのプロバイダの詳細は、アプリケーションで静的に設定することもできるが、実行時に詳細を発見することで、さまざまなプロバイダと対話する際に、より柔軟性と堅牢性を提供することができる。
ディスカバリ・ドキュメントの取得は、単純なHTTP GET
リクエストである。ドキュメントは通常https://<the-domain eg. example.com>/.well-known/openid_configuration
m_network->get(request, this, [this](QRestReply &reply) { if (reply.isSuccess()) { if (auto doc = reply.readJson(); doc && doc->isObject()) m_oidcConfig = doc->object(); // Store the configuration } });
注目すべきは、トークン検証のために、jwks_uriフィールドは現在の(公開)セキュリティ資格情報にアクセスするためのリンクを提供することです。これを使うことで、そのような信用証明書をアプリケーションに直接ハードコードする必要性がなくなります。これは鍵のローテーションにも役立ちます。ベンダは使用する鍵を時々変更するかもしれないので、最新の鍵を確保することは重要です。
鍵の取得も同様に、単純なHTTP GET
:
m_network->get(request, this, [this](QRestReply &reply) { if (reply.isSuccess()) { if (auto doc = reply.readJson(); doc && doc->isObject()) m_jwks = doc; // Use the keys later to verify tokens } });
鍵セットには通常、複数の鍵が含まれている。正しいキーはJWTヘッダーで示される(キーが正しくマッチするように注意しなければならない。キーID(kid
)フィールドをチェックするだけでは適切ではない)。
OpenIDユーザー情報エンドポイント
ユーザー情報にアクセスする別の方法は、OIDCプロバイダーがサポートしていれば、OpenID UserInfo Endpointを使うことです。userinfo の URL は、OpenID Connect Discoveryドキュメントのuserinfo_endpoint
フィールドにあります。
userinfoエンドポイントはIDトークンを使わず、アクセストークンを使ってアクセスします。userinfoへのアクセスは、アクセストークンを使った他のリソースへのアクセスと同様です。
例えば、アクセストークンを受け取って設定したとします:
QNetworkRequestFactory userInfoApi(url); userInfoApi.setBearerToken(m_oauth.token().toLatin1());
その場合、userinfoへのアクセスはHTTP GET
:
m_network->get(userInfoApi.createRequest(), this, [this](QRestReply&reply){if(reply.isSuccess()) {if(autodoc=reply.readJson(); doc&& doc->isObject()) qDebug() << doc->object(); // Use the userinfo } });
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.