カスタム認証
全てのアプリケーションは、デフォルトで匿名ユーザーの接続を許可していて、認証メカニズムはありません。Photonは、アプリケーションにカスタム認証を実装するオプションを提供しています。
Photonのカスタム認証は柔軟性が高く、有名なサードパーティーの認証プロバイダーから、完全に個別のソリューションまで対応しています。
弊社はGitリポジトリで認証プロバイダーの実装例を提供しています。
リポジトリのフォークや、プルリクエストは自由です。
GitHubでソースを確認してください。
認証フロー
以下の図に、認証プロセスの一般的なフローを示します。
- クライアントは、使用する認証プロバイダーと必要な認証データの情報を、
Connect()
でPhoton Serverに渡します。 - Photon Serverは、要求した認証プロバイダーを取得し、以下のいずれかのステップに進みます。
- 認証プロバイダー設定を検出した場合は、ステップ3に進みます。
- 認証プロバイダー設定が検出されない場合は、アプリケーションの設定に応じて、クライアントは接続を許可/拒否されます。
- Photon Serverは、
Connect()
で渡された認証情報で、認証プロバイダーを呼び出します。- 認証プロバイダーがオンラインの場合、ステップ4に進みます。
- 認証プロバイダーがオフラインの場合、該当の認証プロバイダーの設定に応じて、クライアントは接続を許可/拒否されます。
- 認証プロバイダーが認証情報を処理し、Photon Serverに結果を返します。
- 認証結果に応じて、クライアントは正常に認証/拒否されます。
Photon Serverのセットアップ
カスタム認証は、NameServerアプリケーション設定ファイル("deploy\NameServer\bin\NameServer.xml.config")から設定可能です。
以下のスニペットは、カスタム認証プロバイダーの設定例です。
XML
<CustomAuth Enabled="true" AllowAnonymous="false">
<AuthProviders>
<AuthProvider Name="Custom"
AuthenticationType="0"
AuthUrl="https://your-custom-auth-provider-url"
RejectIfUnavailable = "false"/>
<CustomAttributes Key1="Val1"
Key2="Val2">
</CustomAttributes>
</AuthProviders>
</CustomAuth>
Photonネイティブ設定は、以下の通りです。
AllowAnonymous
:認証無しで匿名クライアントの接続を許可するかどうかを示すフラグName
:設定された認証プロバイダーの名前AuthenticationType
:認証プロバイダーの種類AuthUrl
:認証プロバイダーのエンドポイントRejectIfUnavailable
:認証サービスが動作していない場合にクライアントを拒否するかどうかを示すフラグ
<CustomAttributes>
の下のAuthProvider
ノードには、XML属性を追加することも可能です。
これらのキーバリューは、クエリ文字列パラメーターとして送信されます。
上記サンプルでは、(Key1="Val1"
、Key2="Val2"
)になります。
実装
クライアントサイド
クライアントサイドでは、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コールが抑えられ、ログインのワークフローが単純化されるため、非常に有用です。
ごくまれなケースとして、認証で多くのデータを必要とすることがあります。しかしその一方、ほとんどのWebサーバーは、クエリ文字列の文字数や、URLの長さに制限があります。
そのためPhotonでは、クライアント(C# SDK)から明示的にAuthenticationValues.AuthPostData
の値を設定することで、HTTPメソッドをPOSTに変更できるようになっています。
値は、string
・byte[]
・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." }
)