OAuth 2.0の概要

RFC 6749 - OAuth 2.0 Authorization Frameworkは、サードパーティのアプリケーションを使用してサービスを認可するためのプロトコルを規定しています。OAuth 2.0では、トークンを使ってサービスとユーザーから認可を抽象化します。この方法は、サービスオーナーがユーザー認証情報を扱う必要がないため、より安全です。これは、RFC 5849OAuth 1.0を置き換えるものです。

OAuth 2.0フレームワークでは、公開と秘密の2つのクライアントタイプと、認可コードフロー、暗黙のコード付与などのフローが定義されています。典型的な Qt アプリケーションは、パブリック・ネイティブ・アプリケーションとみなされます。パブリッククライアントアプリケーションは、出荷されたバイナリ内に埋め込まれるパスワードなどの機密情報を保持することを信頼できないアプリケーションです。

RFC 8252 OAuth 2.0 for Native Appsは、ネイティブアプリケーションのベストプラクティスをさらに定義しています。具体的には、RFC 8252はブラウザを使った認可フローを推奨しています。そのため、QtNetworkAuth クラスはこのフローの具体的な実装を提供します。

Qt 6.9 の新機能として、QtNetworkAuthRFC 8628- OAuth 2.0 Device Authorization Grant をサポートしています。このデバイスフローは、入力機能が制限されていたり、実用的でないデバイスを対象としています。このフローでは、デバイスの代わりにスマートフォンなどのセカンダリデバイスを使用します。このようなデバイスの例としては、テレビ、メディア・コンソール、機械の HMI、IoT デバイスなどがある。ユーザーは、スマートフォン上のアプリケーションを使用して、デバイスを認可することができる。

次の表は、Qt Network Authorizationでサポートされる2つのOAuth 2.0フローをハイライトしたものです:

アスペクト認証コードフローデバイス認証フロー
ネットワーク接続ネットワーク接続あり
ユーザー・インタラクション同じデバイス上のブラウザ/ユーザーエージェント異なるデバイス上のブラウザ/ユーザーエージェント
リダイレクト処理が必要はいなし
デバイス上の入力機能豊富な入力機能限られた入力機能または入力機能なし
対象デスクトップ・モバイルアプリテレビ、コンソール、HMI、IoTデバイス

OAuth 2.0では、通常ブラウザであるユーザーエージェントを使用する必要があります。詳細はQt OAuth2 Browser Support を参照してください。

OAuth 2.0 クラス

Qt Network Authorization は、具象と抽象の OAuth 2.0 クラスを提供します。抽象クラスはカスタムフローを実装するためのもので、具象クラスは具体的な実装を提供します。

C++ クラスの一覧については、QtNetworkAuth を参照してください。

Qt Network Authorization には、OAuth 2.0 フローを実装するための抽象クラスが 2 つあります:

認証コードの流れ

このセクションは、RFC 6749 - Authorization Codeと RFC 8252 - Authorization Request from a Native Appから、ネイティブアプリケーショ ンのための認可コードフローの概要である。

以下のサンプル・セットアップを考えてみましょう:

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();
}

認証フローのステージ

RFC6749の認可コードフローには、2つの主要なステージがある。リソースの認可(必要なユーザー認証を含む)に続いて、アクセストークン・リクエストがある。オプションで、アクセストークンの使用とアクセストークンの更新が続きます。次の図は、これらのステージを示している:

認証サーバーとブラウザを使用したQtアプリケーションにおける認証プロセスの簡素化

  • 認可段階では、ユーザーが認証され、リソースへのアクセスを認可します。これには、ユーザーによるブラウザの操作が必要です。
  • 認可後、受け取った認可コードを使用してアクセストークンを要求し、オプションでリフレッシュトークンを要求します。
  • アクセストークンが取得されると、アプリケーションはそれを使用して目的のリソースにアクセスします。アクセストークンはリソースリクエストに含まれ、トークンの有効性を確認するのはリソースサーバーです。ベアラートークンをリクエストに含めるには、いくつかの方法があります。HTTPAuthorization ヘッダーにトークンを含める方法は、最も一般的な方法です。
  • アクセストークンのリフレッシュ。アクセストークンは通常、1時間後など比較的早く失効します。アプリケーションがアクセストークンに加えてリフレッシュトークンを受け取った場合、そのリフレッシュトークンを使って新しいアクセストークンを要求することができます。アプリケーションはリフレッシュ・トークンの有効期限を長くすることで、新たな認可段階(つまりブラウザとの再インタラクション)の必要性を避けることができます。

詳細とカスタマイズ

OAuth 2.0のフローは動的であり、仕様を実装するのは最初は難しいかもしれません。下図は、成功する認可コードフローの主な詳細を示しています。

OAuth 2.0フローの詳細(特定のイベントコールを示す

わかりやすくするために、いくつかのシグナルを省略していますが、全体として詳細と主なカスタマイズポイントを示しています。カスタマイズ・ポイントとは、アプリケーションが使用できるさまざまなシグナルとスロット、およびQAbstractOAuth::setModifyParametersFunction() とQAbstractOAuth2::setNetworkRequestModifier() で設定可能なコールバックのことです。

リプライ・ハンドラの選択

どのハンドラを使うかは、redirect_uri要素に依存します。redirect_uri は、認可段階が終了したときにブラウザがリダイレクトされる場所に設定されます。

ネイティブ・アプリケーションで認可応答を受信するために、RFC 8252では主に3種類の応答URIスキームを規定しています: private-use、loopback、httpsです。

  • プライベートURI:OS がアプリケーションにカスタム URI スキームの登録を許可している場合に使用できる。このようなカスタムスキームのURLを開こうとすると、関連するネイティブアプリケーショ ンが開かれる。QOAuthUriSchemeReplyHandler を参照。
  • HTTPS URI:OS がアプリケーションにカスタム HTTPS URL の登録を許可している場合に使用できます。このURLを開こうとすると、関連するネイティブアプリケーショ ンが開かれる。このスキームはOSがサポートしている場合に推奨される。QOAuthUriSchemeReplyHandler を参照のこと。
  • ループバックインターフェース:これらはデスクトップ・アプリケーションや開発中のアプリケーションによく使われる。QOAuthHttpServerReplyHandler は、リダイレクトを処理するローカルサーバーをセットアップする ことで、これらのURIを処理するように設計されている。

選択は次のようないくつかの要因に依存する:

  • 認可サーバーベンダーがサポートするリダイレクトURI。リダイレクトURIは認証サーバベンダによってサポートされる。サポートはベンダ によって異なり、特定のクライアントのタイプやオペレーティングシステムに特化し ていることが多い。また、アプリケーションが公開されているかどうかによってもサポートが異なる場合がある。
  • ターゲットプラットフォームがサポートするリダイレクトURIスキーム。
  • アプリケーション固有のユーザビリティ、セキュリティ、その他の要件。

RFC 8252では、他の方法よりもセキュリティと使いやすさの点で優れているとして、https スキームの使用を推奨している。

OAuth 2.0 デバイス認証グラント

RFC 8628OAuth 2.0 Device Authorization Grant は、入力機能が制限されている接続デバイスや、ユーザーエージェントとブラウザの使用が実用的でない接続デバイスを対象としています。このフローを使用するデバイスの例としては、認可のために外部デバイスを必要とするスマート家電があります。

以下のサンプル設定を考えてみましょう:

m_deviceFlow.setAuthorizationUrl(QUrl(authorizationUrl)); m_deviceFlow.setTokenUrl(QUrl(accessTokenUrl)); m_deviceFlow.setRequestedScopeTokens({scope}); m_deviceFlow.setClientIdentifier(clientIdentifier);// クライアントシークレットの必要性は、認可サーバーに依存するm_deviceFlow.setClientIdentifierShift((authorizationUrl))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つの主要なステージがある。認可の初期化、トークンのポーリング、認可の完了である。オプションで、トークンの使用とトークンのリフレッシュが続きます。以下の図に、これらのステージを示します:

別デバイスのブラウザを使用する Qt アプリケーションにおける承認付与フローの簡素化

  • 認可は、認可サーバーにHTTPリクエストを送信することで初期化される。認証サーバは、応答としてユーザコード、検証URL、およびデバイスコードを提供する。
  • 認可が初期化された後、ユーザは、認可を完了するためのユーザコードと検証用 URL を提供される。エンドユーザのためのメカニズムは様々で、画面上の可視URL、QRコード、電子メールなどである。詳細については、RFC 8628 - User Interactionを参照のこと。
  • エンド・ユーザが認証を完了するのを待っている間、デバイス・フローは認証サーバにトークンをポーリングする。前のステップで受信したデバイス・コードは、認可セッションのマッチングに使用される。ポーリング間隔は認可サーバーによって決定され、通常5秒である。
  • エンドユーザが認可を受諾または拒否すると、認可サーバは要求されたトークンまたは拒否された場合はエラーコードをポーリング要求に応答し、認可は完了する。

詳細とカスタマイズ

次の図に、デバイス認証付与のフローを詳しく示します。この図には、場合によっては必要となる主なカスタマイズ・ポイントが示されている。例えば、独自のパラメータや追加の認証情報などです。

OAuth 2.0デバイスグラントフローの詳細(特定のイベントコールを示す

トークンの更新

リフレッシュトークンを使用するには、認証時に認証サーバがリフレッシュトークンを提供する必要があります。リフレッシュトークンを常に提供するサーバもあれば、全く提供しないサーバもあり、 認証リクエストに特定のscope が存在する場合に提供するサーバもある。

次の図は、トークン・リフレッシュの詳細を示している:

特定のイベントコールを示すリフレッシュトークンの詳細

上図に示すように、トークンをリフレッシュする際には、通常のカスタマイズポイントも利用可能です。

アプリケーションの起動後にトークンをリフレッシュするには、アプリケーションはリフレッシュ・トークンをセキュアに永続化し、QAbstractOAuth2::setRefreshToken で設定する必要があります。QAbstractOAuth2::refreshTokens は、その後、新しいトークンを要求するために呼び出すことができます。

Qt 6.9 の新機能として、アプリケーションは自動的にトークンをリフレッシュすることができます -QAbstractOAuth2::accessTokenAboutToExpireQAbstractOAuth2::autoRefreshQAbstractOAuth2::refreshLeadTime を参照してください。

リフレッシュ・トークンの有効期限は、一般的に認証サーバからは提示されません (サーバのドキュメントを除いて)。リフレッシュ・トークンの有効期限は、数日から数ヶ月、あるいはそれ以上と幅があります。さらに、他のトークンと同様に、リフレッシュ・トークンもユーザによって取り消される可能性があるため、いつでも無効にすることができます。したがって、QAbstractOAuth::requestFailedQAbstractOAuth2::serverReportedErrorOccurred を使って、リフレッシュの失敗を適切に検出することが重要です。

OAuth 2.0のフローは、多くのユーザーとのインタラクションを必要とし、ユーザーエクスペリエンスを阻害する可能性があります。このようなインタラクションを最小化するために、トークンを黙ってリフレッシュすることができる。詳細はRFC 6749 - Refreshing Access Tokensを参照のこと。

Qt OpenID Connectのサポート

OpenID Connect (OIDC)は OAuth 2.0 の上のシンプルな ID レイヤです。OIDC は、ユーザのアイデンティティを認証するために認証サーバを使用することができます。OIDCを使用することで、シンプルなユーザプロファイル情報にアクセスすることも可能です。

Qt の OIDC サポートは、今のところ ID トークンの取得に限られています。IDトークンは、認証イベントに関する主張を含むJSON Web Token (JWT)です。

注: IDトークンの検証やIDトークンの復号化は、現在のところ実装されていません。JWTトークンの署名または検証には、サードパーティのJWTライブラリを使用する必要があります。

アプリケーションが受信したトークンを検証できると仮定すると、トークンは(OIDCプロバイダ自体が信頼できる限り)ユーザーの身元を確実に確立するために使用できます。

ID トークンは機密情報であり、秘密として保持されるべきであり、アクセストークンとは異なる。IDトークンはAPIコールで送信するためのものではありません。アクセストークンはそのためのものです。ベンダーによっては、アクセストークンに同じJWT形式を使用する場合がありますが、同じ形式を使用する実際のIDトークンと混同しないでください。IDトークンの場合、トークンを受け取るクライアントがトークンを検証する責任を負いますが、アクセストークンの場合、検証の責任を負うのはトークンを受け取るリソースサーバーです。

IDトークンの取得

IDトークンの取得は、アクセストークンの取得と似ています。まず、適切なスコープを設定します。認可サーバーのベンダーは、profileemail などの追加スコープ指定子をサポートしている場合がありますが、すべての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トークンを検証することは、認証フローの重要な部分であり、完全に実装されるとやや複雑なタスクになります。OpenID Connect ID Validationを参照してください。

簡単にまとめると、検証は以下のステップで構成されます:

  • 必要に応じてトークンを復号化する(JWEを参照)
  • トークンのヘッダー、ペイロード、署名を抽出する。
  • 署名の検証
  • ペイロードのフィールドの検証(aud, iss, exp, nonce, iat など)

Qtは現在、IDトークンの検証をサポートしていませんが、jwt-cppのようなサードパーティのJWTライブラリがあります。

IDトークン検証の例

ここでは簡単な検証例を示します。前提条件として、開発環境には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トークンを受け取ったら、次はそれを検証する番だ。まず、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トークンは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};

場合によっては、トークンは内部的にJWTトークンを含むJSON Web Encryption (JWE)として暗号化されていることがあります。この場合、トークンを最初に復号化する必要があります。

OpenID Connectディスカバリー

OpenID Connect Discoveryは、OpenIDプロバイダと対話するために必要なOpenIDプロバイダの詳細を発見する手段を定義しています。これにはauthorization_endpointtoken_endpoint URL などの情報が含まれる。

これらのプロバイダの詳細は、アプリケーションで静的に設定することもできるが、実行時に詳細を発見することで、さまざまなプロバイダと対話する際に、より柔軟性と堅牢性を提供することができる。

ディスカバリー・ドキュメントの取得は、単純なHTTP GETリクエストである。ドキュメントは通常、https://<domain name>/.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 UserInfoエンドポイント

ユーザー情報にアクセスする別の方法は、OIDCプロバイダーがサポートしていれば、OpenID Connect 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.