カスタム認証

全てのアプリケーションは、デフォルトで匿名ユーザーの接続を許可していて、認証メカニズムはありません。Photonは、アプリケーションにカスタム認証を実装するオプションを提供しています。

Photonのカスタム認証は柔軟性が高く、有名なサードパーティーの認証プロバイダーから、完全に個別のソリューションまで対応しています。

  • Exit GamesがホスティングするFacebook認証プロバイダーは、チュートリアルをご覧ください
  • ユーザーが独自に構築・ホスティングしているカスタム認証プロバイダーについて、
    弊社はGitリポジトリで認証プロバイダーの実装例を提供しています。
    リポジトリのフォークや、プルリクエストは自由です。
    GitHubでソースを確認してください。

認証フロー

以下の図に、認証プロセスの一般的なフローを示します。

Photon Cloud: Custom Authentication Flow Diagram
カスタム認証フロー図
  1. クライアントは、使用する認証プロバイダーと必要な認証データの情報を、Connect()でPhoton Serverに渡します。
  2. Photon Serverは、要求した認証プロバイダーを取得し、以下のいずれかのステップに進みます。
    • 認証プロバイダー設定を検出した場合は、ステップ3に進みます。
    • 認証プロバイダー設定が検出されない場合は、アプリケーションの設定に応じて、クライアントは接続を許可/拒否されます。
  3. Photon Serverは、Connect()で渡された認証情報で、認証プロバイダーを呼び出します。
    • 認証プロバイダーがオンラインの場合、ステップ4に進みます。
    • 認証プロバイダーがオフラインの場合、該当の認証プロバイダーの設定に応じて、クライアントは接続を許可/拒否されます。
  4. 認証プロバイダーが認証情報を処理し、Photon Serverに結果を返します。
  5. 認証結果に応じて、クライアントは正常に認証/拒否されます。

Photon Cloudのセットアップ

Photonアプリケーションのダッシュボードから、全ての認証プロバイダーをセットアップできます。アプリケーションの詳細ページに進み、カスタム認証セクションを開きます。

カスタム認証の設定は、変更が反映されるまでにしばらく時間がかかります。

認証プロバイダーの追加

認証プロバイダーの設定は、Photonアプリケーションのダッシュボードから簡単に行えます。

Photon Cloud: Custom Authentication Creation
カスタム認証プロバイダーの作成

ここでは、認証URLを入力したり、認証サービスがオフラインまたは何らかの理由で動作していない場合に、クライアントを拒否するかどうかを決めることができます。さらに、認証サービスのリクエストごとに送信するキーバリューペア(クエリ文字列パラメーター)を、オプションで追加できます。

ベストプラクティスとして、クライアントからは見えないセンシティブで固定のキーバリューペアのみを、ダッシュボードで設定してください。 例:
  • リクエストが実際のPhoton Serverから来ているかを確認するための、APIのパブリックキー
  • 更新を適切に処理するための、カスタム認証のAPIバージョン

また、認証プロバイダーの設定に関係なく、匿名クライアントの接続を許可/拒否することもできます。これはデフォルトでは有効になっているため、アプリケーションで認証する/しないにかかわらず、初期設定では全てのクライアントの接続が承認されます。これは、Photonアプリケーションのダッシュボードで、アプリケーションの詳細ページの認証セクションから確認できます。このオプションは、少なくとも1つの認証プロバイダーを追加している場合のみ表示されます。認証プロバイダーを設定していない場合は、(デフォルト値の有効のまま)非表示になります。

Photon Cloud: Custom Authentication, Allow Anonymous Clients
カスタム認証設定時に、匿名クライアントを許可するかどうか

認証プロバイダーの更新/削除

アプリケーションの詳細ページから、既存の認証プロバイダーを選択して編集することもできます。編集フォームでは、設定の更新や、認証プロバイダーを完全に削除することができます。

Photon Cloud: Custom Authentication Editing
既存のカスタム認証プロバイダーの更新/削除

実装

Photon CloudでFacebook認証を使用している場合、この部分はスキップしてください。

クライアントサイド

クライアントサイドでは、APIでカスタム認証を処理します。これは、関連するパラメーターと、対象のカスタム認証サービスを一度設定するだけです。セットアップが完了したら、接続とその後のエラー処理を行います。

C#

AuthenticationValues authValues = new AuthenticationValues();
authValues.AuthType = CustomAuthenticationType.Custom;
authValues.AddAuthParameter("user", userId);
authValues.AddAuthParameter("pass", pass);
authValues.UserId = userId; // this is required when you set UserId directly from client and not from web service
loadBalancingClient.AuthValues = authValues;
// connect

C++

ExitGames::Common::JString params = "user=" + userId + "&pass=" + pass;
ExitGames::LoadBalancing::AuthenticationValues authenticationValues;
authenticationValues.setType(ExitGames::LoadBalancing::CustomAuthenticationType::CUSTOM);
authenticationValues.setParameters(params);
authenticationValues.setUserId(userId); // this is required when you set UserId directly from client and not from web service
// pass authenticationValues as parameter on connect

JavaScript

var queryString = "user=" + userId + "&pass=" + pass;
var type = Photon.LoadBalancing.Constants.CustomAuthenticationType.Custom;
loadBalancingClient.setCustomAuthentication(queryString, type);
// connect

簡単のため、このスニペットでは非常に基本的なパスワードベースの認証資格情報を選択しています。
その結果、クエリ文字列は?user={user}&pass={pass}になります。
一般的に、認証資格情報は値のペアで、最初の値はユニークな識別子(ユーザーID、ユーザー名、メールアドレスなど)、もう一つの値は「真正性の証明」(ハッシュ化されたパスワード、鍵、シークレット、トークンなど)です。
セキュリティ上の理由から、平文のパスワードは送信しないでください。

認証オペレーション

認証オペレーションによって、認証の値が実際にサーバーに送信されます。
これは通常、API内部で使用され、開発者が記述するクライアントのコードでは直接使用しません。

重要な注意点は、サーバーへの接続には、常に認証ステップが含まれることです。
まず、オペレーションは暗号化された実際の認証の値を送信します。その後、サーバーはPhotonが提供する暗号化されたトークンを自動的に使用するようになります。

サーバーサイド

Webサーバーは認証リクエストを受信したら、クエリパラメーターのチェックとバリデーションを行うべきです。
例えば、認証資格情報をデータベースに存在する既存の情報と比較したりします。

受信したパラメーターが見つからないまたは無効な値だった場合、{ "ResultCode": 3, "Message": "Invalid parameters." }のような結果が返ります。

バリデーションが完了した後は、以下のような結果が返ります。

  • 成功:{ "ResultCode": 1, "UserId": <userId> }
  • 失敗:{ "ResultCode": 2, "Message": "Authentication failed. Wrong credentials." }

高度な機能

ユーザー認証以外にも、認証プロバイダーから追加情報を返すことができます。
これを行うには、クライアントと、認証の役割を担うWebサービス間に、何らかのプロトコルを確立する必要があります。

サーバーへのデータ送信

最も簡単でシンプルな方法は「オール・オア・ナッシング」方式で、クライアントに固定数の変数を返すか、何も返さないかを選択します。
一部のユースケースでは、より複雑なアプローチとして、クライアントがリクエストした内容に応じた「オンデマンド」で、データを返すWebサービスが必要になります。
ここでは、クライアントからWebサービスにデータを送信する方法を説明します。
データには、必須の認証資格情報に加えて、任意の追加パラメーターを含めます。
追加パラメーターは、サーバーサイドで取得可能なデータの一部をリクエストし、認証レスポンスに含めて返したい時などに、特に使用されます。
これにより、余分なAPIコールが抑えられ、ログインのワークフローが単純化されるため、非常に有用です。

認証に使用されるデフォルトのHTTPメソッドはGETになるため、パラメーターはクエリ文字列のキーバリューペアとして送信されます。 最終的なURLには、クライアントやダッシュボードから設定されたキーバリューペアを「結合」したものが含まれます。 両方の場所で同じキーが使用されている場合、ダッシュボードの値のみが送信されます。 ベストプラクティスとして、クライアントからは見えないセンシティブで固定のキーバリューペア(例えば、APIキー、APIバージョン)のみを、ダッシュボードで設定してください。 また、ダッシュボードのキーバリューは、クライアントを更新することなく、オンザフライで変更できます。

ごくまれなケースとして、認証で多くのデータを必要とすることがあります。しかしその一方、ほとんどのWebサーバーは、クエリ文字列の文字数や、URLの長さに制限があります。
そのためPhotonでは、クライアント(C# SDK)から明示的にAuthenticationValues.AuthPostDataの値を設定することで、HTTPメソッドをPOSTに変更できるようになっています。
値は、stringbyte[]Dictionary<string, object>型が有効です。
Dictionary<string, object>の場合、ペイロードはJSON文字列に変換され、HTTPリクエストのContent-Typeは「application/json」に設定されます。
C# SDKのAuthenticationValuesクラスは、サポートする型にそれぞれsetterメソッドが用意されています。

これは要件や制約になることがあるため、認証リクエストをWebサービスからのPOSTメソッドとして受信したいユーザーのために、POSTメソッドのオプションも提供されています。

つまり、認証パラメーターの送信は、クエリ文字列またはPOSTのデータ、もしくはその両方を自由に使用できます。
以下の表に、有効な組み合わせを示します。

AuthPostData AuthGetParameters HTTP method
null * GET
empty string * GET
string (not null, not empty) * POST
byte[] (not null, can be empty) * POST
Dictionary<string, object> (not null, can be empty) * POST (Content-Type="application/json")

クライアントへのデータ返送

Photon Serverは、クライアントとWebサービス間のプロキシになるので、どの変数がPhoton Serverで処理されるのかを覚えておく必要があります。

Photon Serverが受信する全てのHTTPレスポンスのように、Webサーバーは、ResultCodeと任意のMessageを含むJSONオブジェクトを返す必要があります。
Photon Serverが、認証時にWebサービスから受信可能なリストは、以下の通りです。

  • UserId:この値は、認証のパラメーターや、クライアント側からのリクエストとして使用できます。Photon Serverが受信した値は、必ずクライアントに転送されます。AuthenticationValues.UserIdが設定されていない場合は、ランダムに生成されたUserIdがクライアントに返送されます。これでクライアントのUserIdの値が上書きされた以降、その値は変更できなくなります。この値は、ResultCodeが1の場合のみ返されます。(例:{ "ResultCode": 1, "UserId": "SomeUniqueStringId" }
  • Nickname:この値は、認証のパラメーターや、クライアント側からのリクエストとして使用できます。Webサービスから返された値で、クライアントのNicknameの値は上書きされますが、それ以降もクライアント側で変更可能です。この値は、ResultCodeが1の場合のみ返されます。(例:{ "ResultCode": 1, "UserId": "SomeUniqueStringId", "Nickname": "SomeNiceDisplayName" }
  • AuthCookie:セキュアデータとも呼ばれる、Webサービスで返されるJSONオブジェクトです。暗号化されたトークンに埋め込まれるため、クライアント側からアクセスすることはできません。WebhookまたはWebRPCのHTTPリクエストで、後から送信することもできます。この値は、ResultCodeが1の場合のみ返されます。(例:{ "ResultCode": 1, "UserId": "SomeUniqueStringId", "AuthCookie": { "SecretKey": "SecretValue", "Check": true, "AnotherKey": 1000 } }
  • Data:クライアントに返したい全ての追加の値を含むJSONオブジェクトです。配列やオブジェクトの入れ子には対応していないことに注意してください。この値は、ResultCodeが0か1の場合のみ返されます。(例:{ "ResultCode": 0, "Data": { "S": "Vpqmazljnbr=", "A": [ 1, -5, 9 ] } }

ResultCodeのみが必須の戻り値で、他の変数は任意となります。
以下の表に、Webサーバーで返すことができる変数を示します。

ResultCode Description UserId Nickname AuthCookie Data
0 Authentication incomplete, only Data returned.*
1 Authentication successful. (optional) (optional) (optional) (optional)
2 Authentication failed. Wrong credentials.
3 Invalid parameters.

*:例えば、OAuth 2.0や2段階認証を実装するのに有用です。

クライアントからのデータ読み込み

レスポンスから返された値を取得する方法は、以下のコードスニペットの通りです。

C#

// implement callback from appropriate interface or override from class implementing it
void OnCustomAuthenticationResponse(Dictionary<string, object> data)
{
    // here you can access the returned data
}

C++

// In case of multi-leg authentication simply implement:
Listener::onCustomAuthenticationIntermediateStep();  
// in case of ResultCode:0 LoadBalancing::Client will call that function and pass the intermediate data as parameter to it

データ型の変換

ここでは、Photon ServerとWebサービス間で通信するデータ型のみを説明します。
クライアントとPhoton Server間で通信するデータ型の詳細は、Photonのシリアライゼーションをご覧ください。

Photon Server -> Webサービス

C# / .NET (Photon supported types) JavaScript / JSON
byte number
short
int
long
double
bool bool
string string
byte[] (byte array length < short.MaxValue) string (Base64 encoded)
T[] (array of supported type T, length < short.MaxValue) array
Hashtable (of supported types, count < short.MaxValue, preferably Photon implementation) object
Dictionary (keys and values of supported types, count < short.MaxValue) object
null null

リクエストデータ(型を連結済み)のサンプル

Photon Serverからの送信

JSON

{
    "(Dictionary<String,Object>)Dictionary":{
        "(Int32)dk_int":"1",
        "(String)dk_str":"dv2",
        "(Boolean)dk_bool":"True"
    },
    "(Hashtable)Hashtable":{
        "(Byte)hk_byte":"255",
        "(Object[])hk_array":[
            "(Int32)0",
            "(String)xy",
            "(Boolean)False"
        ],
        "hk_null":"null"
    },
    "null":"null",
    "(String[])string[]":[
        "PUN",
        "TB",
        "RT",
        "Bolt",
        "Chat"
    ],
    "(Byte[])byte[]":[
        "255",
        "0"
    ],
    "(Int16[])short[]":[
        "-32768",
        "32767"
    ],
    "(Int32[])int[]":[
        "-2147483648",
        "2147483647"
    ],
    "(Int64[])long[]":[
        "-9223372036854775808",
        "9223372036854775807"
    ],
    "(Single[])float[]":[
        "-3.402823E+38",
        "3.402823E+38"
    ],
    "(Double[])double[]":[
        "-1.79769313486232E+308",
        "1.79769313486232E+308"
    ],
    "(Boolean[])bool[]":[
        "True",
        "False"
    ]
}

Webサービスでの読み込み

JSON

{
    "(object)Dictionary":{
        "dk_int":"(number)1",
        "dk_str":"(string)dv2",
        "dk_bool":"(boolean)true"
    },
    "(object)Hashtable":{
        "(number)hk_byte":"255",
        "(array)hk_array":[
            "(number)0",
            "(string)xy",
            "(boolean)false"
        ],
        "hk_null":null
    },
    "null":null,
    "(array)string[]":[
        "(string)PUN",
        "(string)TB",
        "(string)RT",
        "(string)Bolt",
        "(string)Chat"
    ],
    "byte[]":"(string)/wA=",
    "(array)short[]":[
        "(number)-32768",
        "(number)32767"
    ],
    "(array)int[]":[
        "(number)-2147483648",
        "(number)2147483647"
    ],
    "(array)long[]":[
        "(number)-9223372036854776000",
        "(number)9223372036854776000"
    ],
    "(array)float[]":[
        "(number)-3.40282347e+38",
        "(number)3.40282347e+38"
    ],
    "(array)double[]":[
        "(number)-1.7976931348623157e+308",
        "(number)1.7976931348623157e+308"
    ],
    "(array)bool[]":[
        "(boolean)true",
        "(boolean)false"
    ]
}

Webサービス -> Photon Server

JavaScript/JSONの型と、C#/.Netの型との対応表は、以下の通りです。

JavaScript / JSON C# / .Net
object Dictionary
array object[] (array of objects)
number (integral) long
number (floating) double
string string
boolean bool
null (not a type) null
undefined (when sent) null

レスポンスデータ(型を連結済み)のサンプル

Webサービスからの送信

JSON

{
    "(object)number": {
        "(number)MAX_VALUE": "1.7976931348623157e+308",
        "(number)MIN_VALUE": "5e-324"
    },
    "(object)object": {
        "(string)string": "xyz",
        "null": null,
        "(boolean)bool": "false",
        "(undefined)undefined": "undefined",
        "(number)float": "-3.14",
        "(number)integer": "123456"
    },
    "(array)array": [
        "(string)xyz",
        "(number)0",
        "(boolean)true",
        null,
        "(undefined)undefined"
    ]
}

Photon Serverでの読み込み

JSON

{
    "(Dictionary<String,Object>)number":{
        "(Double)MAX_VALUE":"1.79769313486232E+308",
        "(Double)MIN_VALUE":"4.94065645841247E-324"
    },
    "(Dictionary<String,Object>)object":{
        "(String)string":"xyz",
        "null":"null",
        "(Boolean)bool":"False",
        "(Double)float":"-3.14",
        "(Int64)integer":"123456"
    },
    "(Object[])array":[ 
        "(String)xyz",
        "(Int64)0",
        "(Boolean)True",
        "null",
        "null"
    ]
}

トラブルシューティング

カスタム認証に失敗すると、以下のコールバックがトリガーされます。

C#

void OnCustomAuthenticationFailed(string debugMessage)
{
   // The `debugMessage` could be what the authentication provider returned.
}

ダッシュボードで設定した認証URLがHTTPエラーを返す場合、Photon Serverはオーバーヘッドを避けるため、短時間だけ認証コールを一時停止します。
URLを設定またはテストする時は、この「バックオフ」の時間を考慮してください。

ベストプラクティス

  • 認証プロバイダーから返される結果には、判読可能なMessageを含めてください。特に認証が失敗したケースでは、デバッグの手間を大幅に省くことができます。
  • 固定のキーバリューペアは、クライアント側からではなくダッシュボードから設定してください。クエリ文字列のキーが重複するのを防ぐことができます。
  • セキュリティ上の理由から、認証パラメーターで平文のパスワードを送信しないでください。
  • ダッシュボードからクエリ文字列パラメーターを設定してください。リクエストのオリジンが確認できます。
  • パラメーターの設定には、AuthenticationValuesのメソッドを使用し、AuthGetParametersの値を直接変更しないでください。クエリ文字列が不正な形式になるのを防ぐことができます。

ユースケース例:古いクライアントバージョンをブロック

カスタム認証によって、古いバージョン(または不正なバージョン)を使用するクライアントの接続を拒否し、ユーザーにアップデートを促す特定のエラーを返すことができます。
そのためには、カスタム認証リクエストでバージョン情報を送信する必要があります。クエリ文字列パラメーターまたはPOSTデータ引数のどちらで送信するかは、ユーザー自身で決めることができます。
クエリ文字列パラメーターを使用する例は、以下の通りです。

C#

string version = lbClient.AppVersion;
lbClient.AuthValues = new AuthenticationValues();
lbClient.AuthValues.AuthType = CustomAuthenticationType.Custom;
lbClient.AuthValues.AddAuthParameter("version", version);

カスタム認証のURLがhttps://example.comなら、リクエストはhttps://example.com?version={version}で送信されます。
認証プロバイダーの実装で、バージョンの取得と比較を行ってください。
そのバージョンを許可するなら{ "ResultCode": 1 }を返します。
許可しないなら、ResultCodeで任意の値(1以外)と、できればメッセージを返してください。
(例:{ "ResultCode": 5, "Message": "Version not allowed." }

Back to top