カスタム認証
全てのアプリケーションは、デフォルトで匿名ユーザーの接続を許可していて、認証メカニズムはありません。Photonは、アプリケーションにカスタム認証を実装するオプションを提供しています。
Photonのカスタム認証は柔軟性が高く、有名なサードパーティーの認証プロバイダーから、完全に個別のソリューションまで対応しています。
- ユーザーが独自に構築・ホスティングしているカスタム認証プロバイダーについて、
弊社はGitリポジトリで認証プロバイダーの実装例を提供しています。
リポジトリのフォークや、プルリクエストは自由です。
GitHubでソースを確認してください。
認証フロー
以下の図に、認証プロセスの一般的なフローを示します。
- クライアントは、使用する認証プロバイダーと必要な認証データの情報を、
Connect()
でPhoton Serverに渡します。これは、Photon Fusion内部で処理されます。 - Photon Serverは、要求した認証プロバイダーを取得し、以下のいずれかのステップに進みます。
- 認証プロバイダー設定を検出した場合は、ステップ3に進みます。
- 認証プロバイダー設定が検出されない場合は、アプリケーションの設定に応じて、クライアントは接続を許可/拒否されます。
- Photon Serverは、
Connect()
で渡された認証情報で、認証プロバイダーを呼び出します。- 認証プロバイダーがオンラインの場合、ステップ4に進みます。
- 認証プロバイダーがオフラインの場合、該当の認証プロバイダーの設定に応じて、クライアントは接続を許可/拒否されます。
- 認証プロバイダーが認証情報を処理し、Photon Serverに結果を返します。
- 認証結果に応じて、クライアントは正常に認証/拒否されます。
Photon Cloudのセットアップ
Photonアプリケーションのダッシュボードから、全ての認証プロバイダーをセットアップできます。アプリケーションの詳細ページに進み、カスタム認証セクションを開きます。
認証プロバイダーの追加
認証プロバイダーの設定は、Photonアプリケーションのダッシュボードから簡単に行えます。
ここでは、認証URLを入力したり、認証サービスがオフラインまたは何らかの理由で動作していない場合に、クライアントを拒否するかどうかを決めることができます。さらに、認証サービスのリクエストごとに送信するキーバリューペア(クエリ文字列パラメーター)を、オプションで追加できます。
- リクエストが実際のPhoton Serverから来ているかを確認するための、APIのパブリックキー
- 更新を適切に処理するための、カスタム認証のAPIバージョン
また、認証プロバイダーの設定に関係なく、匿名クライアントの接続を許可/拒否することもできます。これはデフォルトでは有効になっているため、アプリケーションで認証する/しないにかかわらず、初期設定では全てのクライアントの接続が承認されます。これは、Photonアプリケーションのダッシュボードで、アプリケーションの詳細ページの認証セクションから確認できます。このオプションは、少なくとも1つの認証プロバイダーを追加している場合のみ表示されます。認証プロバイダーを設定していない場合は、(デフォルト値の有効のまま)非表示になります。
認証プロバイダーの更新/削除
アプリケーションの詳細ページから、既存の認証プロバイダーを選択して編集することもできます。編集フォームでは、設定の更新や、認証プロバイダーを完全に削除することができます。
実装
クライアントサイド
クライアントサイドでは、APIでカスタム認証を処理します。これは、関連するパラメーターと、対象のカスタム認証サービスを一度設定するだけです。セットアップが完了したら、接続とその後のエラー処理を行います。
例:
Photon Fusionは、NetworkRunner
を起動する時に認証資格情報を使用できます。ピアは、Photon Cloudに接続し、ロビーへの参加やセッションの作成などをするために認証を行います。
Fusionは、認証資格情報をPhoton Cloudに送信し、カスタム認証サーバーからのデータを処理します。
NetworkRunner.StartGame
を呼び出す時にAuthenticationValues
を作成して渡す方法は、以下のようなコードになります。
C#
using Fusion;
using Fusion.Sockets;
using Fusion.Photon.Realtime;
//...
public Task<StartGameResult> StartRunner(NetworkRunner runner, GameMode gameMode, string sessionName) {
// Create a new AuthenticationValues
AuthenticationValues authentication = new AuthenticationValues();
// Setup
authentication.AuthType = CustomAuthenticationType.Custom;
authentication.AddAuthParameter("user", "user");
authentication.AddAuthParameter("pass", "pass");
return runner.StartGame(new StartGameArgs() {
SessionName = sessionName,
GameMode = gameMode,
SceneObjectProvider = GetSceneProvider(runner),
AuthValues = authentication // pass the AuthenticationValues
});
}
簡単のため、このスニペットでは非常に基本的なパスワードベースの認証資格情報を選択しています。
その結果、クエリ文字列は?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" }
)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段階認証を実装するのに有用です。
クライアントからのデータ読み込み
Photon Fusionを起動すると、Photon Cloudに自動的に接続し、前述の認証プロセスを実行します。
このプロセスが完了すると、認証サーバーから送信された全てのカスタムデータは、INetworkRunnerCallbacks.OnCustomAuthenticationResponse
の実装を通して、ピアに通知されます。
レスポンスから返された値を取得する方法は、以下のコードスニペットの通りです。
C#
using System;
using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Sockets;
public class SimulationEvents : SimulationBehaviour, INetworkRunnerCallbacks {
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) {
foreach (var item in data.Keys) {
Debug.Log($"{item}={data[item]}");
}
}
// all other methods from INetworkRunnerCallbacks
}
データ型の変換
ここでは、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"
]
}
トラブルシューティング
カスタム認証が失敗すると、Photon Fusionは停止し、INetworkRunnerCallbacks
を通して適切なShutdownReason
が通知されます。
C#
using System;
using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Sockets;
public class SimulationEvents : SimulationBehaviour, INetworkRunnerCallbacks {
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) {
Debug.LogWarning($"{nameof(OnShutdown)}: {nameof(shutdownReason)}: {shutdownReason}");
}
// all other methods from INetworkRunnerCallbacks
}
ダッシュボードで設定した認証URLがHTTPエラーを返す場合、Photon Serverはオーバーヘッドを避けるため、短時間だけ認証コールを一時停止します。
URLを設定またはテストする時は、この「バックオフ」の時間を考慮してください。
ベストプラクティス
- 認証プロバイダーから返される結果には、判読可能な
Message
を含めてください。特に認証が失敗したケースでは、デバッグの手間を大幅に省くことができます。 - 固定のキーバリューペアは、クライアント側からではなくダッシュボードから設定してください。クエリ文字列のキーが重複するのを防ぐことができます。
- セキュリティ上の理由から、認証パラメーターで平文のパスワードを送信しないでください。
- ダッシュボードからクエリ文字列パラメーターを設定してください。リクエストのオリジンが確認できます。
- パラメーターの設定には、
AuthenticationValues
のメソッドを使用し、AuthGetParameters
の値を直接変更しないでください。クエリ文字列が不正な形式になるのを防ぐことができます。
ユースケース例:古いクライアントバージョンをブロック
カスタム認証によって、古いバージョン(または不正なバージョン)を使用するクライアントの接続を拒否し、ユーザーにアップデートを促す特定のエラーを返すことができます。
そのためには、カスタム認証リクエストでバージョン情報を送信する必要があります。クエリ文字列パラメーターまたはPOSTデータ引数のどちらで送信するかは、ユーザー自身で決めることができます。
クエリ文字列パラメーターを使用する例は、以下の通りです。
C#
using Fusion;
using Fusion.Sockets;
using Fusion.Photon.Realtime;
//...
public Task<StartGameResult> StartRunner(NetworkRunner runner, GameMode gameMode, string sessionName) {
var version = GetGameVersion(); // your custom implementation to get the Game Version
// Create a new AuthenticationValues
AuthenticationValues authentication = new AuthenticationValues();
// Setup
authentication.AuthType = CustomAuthenticationType.Custom;
authentication.AddAuthParameter("version", version);
return runner.StartGame(new StartGameArgs() {
SessionName = sessionName,
GameMode = gameMode,
SceneObjectProvider = GetSceneProvider(runner),
AuthValues = authentication
});
}
カスタム認証のURLがhttps://example.com
なら、リクエストはhttps://example.com?version={version}
で送信されます。
認証プロバイダーの実装で、バージョンの取得と比較を行ってください。
そのバージョンを許可するなら{ "ResultCode": 1 }
を返します。
許可しないなら、ResultCode
で任意の値(1以外)と、できればメッセージを返してください。
(例:{ "ResultCode": 5, "Message": "Version not allowed." }
)