This document is about: VOICE 2
SWITCH TO

사용자 지정 인증

기존의 인증 제공자 중 어떤 것도 요구사항에 맞지 않거나 사용자 식별을 위해 다른 서비스를 통합하고 싶다면(자체 사용자 백엔드 포함) Photon의 커스텀 인증을 사용합니다.

간단히 말해, 커스텀 인증 제공자는 여러분이 직접 구축하고 관리하는 웹 서비스입니다. Photon 서버는 설정된 URL로 인증 값을 전달하고 잠시 응답을 기다립니다. 결과에 따라 클라이언트가 Photon에 연결되거나 거부됩니다. 최선의 경우, 백엔드가 userID를 정의하고(선택적으로) 추가 정보를 안전하게 전달해 서버 플러그인에서 사용할 수 있습니다.

샘플 구현

인증 제공자의 샘플 구현을 Git 저장소로 제공합니다.
원하시면 저장소를 포크하고 Pull Request를 보내주세요.

소스는 GitHub에서 확인할 수 있습니다.

인증 흐름

다음 단계는 인증 프로세스의 일반적인 흐름을 설명합니다.

Photon Cloud: Custom Authentication Flow Diagram
커스텀 인증 플로우 다이어그램
  1. 클라이언트는 어떤 인증 제공자를 사용할지와 필요한 인증 데이터를 Photon 서버로(클라이언트가 연결할 때) 전달합니다.
  2. Photon 서버는 애플리케이션에 설정된 인증 제공자를 조회하고 다음 중 하나를 수행합니다.
    • 인증 제공자 구성이 발견됨 → 3단계로 진행.
    • 인증 제공자 구성이 없음 → 애플리케이션 설정에 따라 클라이언트를 허용하거나 거부.
  3. Photon 서버는 Connect()와 함께 전달된 인증 정보를 사용해 인증 제공자를 호출합니다.
    • 인증 제공자가 온라인임 → 4단계로 진행.
    • 인증 제공자가 오프라인임 → 해당 제공자 설정에 따라 클라이언트를 허용하거나 거부.
  4. 인증 제공자는 인증 정보를 처리하고 결과를 Photon 서버에 반환합니다.
  5. 인증 결과에 따라 클라이언트는 인증에 성공하거나 거부됩니다.

구현

Photon Cloud에서 Facebook 인증을 사용한다면 이 부분은 건너뛰어도 됩니다.

클라이언트 측

클라이언트 측에서는 한 번만 관련 파라미터와 커스텀 인증 서비스를 설정하면 API가 커스텀 인증을 처리합니다.
설정을 마친 뒤에는 연결하고, 발생 가능한 오류를 처리하면 됩니다.

예시:

서버 측

웹 서버가 인증 요청을 수신하면 쿼리 파라미터를 검사하고 검증해야 합니다.
예를 들어, 자격 증명을 데이터베이스에 저장된 기존 값과 비교할 수 있습니다.

수신한 파라미터가 없거나 유효하지 않을 경우, 다음 결과를 반환해야 합니다: { "ResultCode": 3, "Message": "Invalid parameters." }

검증을 마친 뒤 결과는 다음 형식으로 반환해야 합니다:

  • 성공: { "ResultCode": 1, "UserId": <userId> }
  • 실패: { "ResultCode": 2, "Message": "Authentication failed. Wrong credentials." }

고급 특징

사용자 인증 외에도, 인증 제공자가 추가 정보를 반환할 수 있습니다.
이를 위해서는 클라이언트와 웹 서비스(“authenticator” 역할) 간에 어떤 형태의 프로토콜을 정해야 합니다.

서버로 데이터 전송

가장 쉽고 간단한 방법은 “전부 혹은 전무” 전략입니다:
클라이언트에 정해진 개수의 변수들을 반환할지 말지만 결정합니다.
하지만 어떤 사용 사례에서는 클라이언트 요청에 따라 웹 서비스가 “온디맨드”로 데이터를 반환해야 할 수도 있습니다.
이 하위 섹션은 클라이언트가 웹 서비스로 데이터를 보내는 방법을 설명합니다.
데이터는 인증에 필요한 자격 증명에 더해 추가 파라미터가 될 수 있습니다.
이 추가 파라미터는 서버 측에서 이용 가능한 일부 데이터를 인증 응답에 포함해 반환하도록 요청하는 데 사용할 수 있습니다.
이는 추가 API 호출을 줄이고 로그인 워크플로를 단순화하는 데 유용합니다.

드물게 인증에 많은 데이터를 요구하는 경우가 있습니다.
한편 대부분의 웹 서버는 쿼리 스트링 문자 수나 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 메서드를 제공합니다.

요건이나 제약으로 인해 필요할 수 있으므로, 웹 서비스가 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 서버는 클라이언트와 웹 서비스 사이의 프록시이므로, Photon 서버가 처리할 수 있는 변수들을 염두에 두어야 합니다.

모든 HTTP 응답과 마찬가지로, 웹 서버는 ResultCode와 선택적 Message를 포함한 JSON 객체를 반환해야 합니다.
아래는 인증 중에 Photon 서버가 웹 서비스로부터 기대할 수 있는 항목 목록입니다.

  • UserId:
    인증 자체의 파라미터로 사용되거나 클라이언트에서 요청될 수 있습니다.
    Photon 서버가 수신하면 항상 클라이언트로 전달됩니다.
    그렇지 않고 AuthenticationValues.UserId가 처음에 설정되지 않았다면, 무작위로 생성된 UserId가 클라이언트에 반환됩니다.
    이는 클라이언트의 UserId 값을 덮어쓰며 이후 변경할 수 없습니다.
    ResultCode 값 1인 경우에만 반환해야 합니다.
    예: { "ResultCode": 1, "UserId": "SomeUniqueStringId" }
  • Nickname:
    인증 파라미터로 사용되거나 클라이언트에서 요청될 수 있습니다.
    웹 서비스에서 반환되면 클라이언트의 Nickname 값을 덮어씁니다.
    이후에도 클라이언트에서 Nickname을 변경할 수 있습니다.
    ResultCode 값 1인 경우에만 반환해야 합니다.
    예: { "ResultCode": 1, "UserId": "SomeUniqueStringId", "Nickname": "SomeNiceDisplayName" }
  • AuthCookie:
    보안 데이터라고도 하며, 웹 서비스에서 반환되지만 클라이언트에서 직접 접근할 수 없습니다(암호화된 토큰에 포함됨).
    이후 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뿐이며, 나머지는 모두 선택 사항입니다.
아래 표는 웹 서버가 반환할 수 있는 내용을 요약합니다.

*: 예를 들어 OAuth 2.0 구현이나 2단계 인증 등에 유용할 수 있습니다.

클라이언트의 데이터 읽기

다음은 응답에서 반환된 값을 얻는 코드 스니펫입니다:

데이터 타입 변환

이 섹션에서는 Photon 서버와 웹 서비스간의 데이터 교환의 타입에 대해서만 설명합니다.
클라이언트와 Photon 서버간의 데이터 타입에 대해서는 Photon 의 직렬화 페이지를 참고 하시기 바랍니다.

Photon 서버 -> 웹 서비스

C# / .NET (Photon 지원 타입) JavaScript / JSON
byte number
short
int
long
double
bool bool
string string
byte[] (byte 배열 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 서버 전송:

JavaScript

{
    "(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":[
            "(Object)0",
            "(Object)xy",
            "(Object)False"
        ],
        "hk_null":"null"
    },
    "null":"null",
    "(String[])string[]":[
        "(String)PUN",
        "(String)TB",
        "(String)RT",
        "(String)Bolt",
        "(String)Chat"
    ],
    "(Byte[])byte[]":[
        "(Byte)255",
        "(Byte)0"
    ],
    "(Int16[])short[]":[
        "(Int16)-32768",
        "(Int16)32767"
    ],
    "(Int32[])int[]":[
        "(Int32)-2147483648",
        "(Int32)2147483647"
    ],
    "(Int64[])long[]":[
        "(Int64)-9223372036854775808",
        "(Int64)9223372036854775807"
    ],
    "(Single[])float[]":[
        "(Single)-3.402823E+38",
        "(Single)3.402823E+38"
    ],
    "(Double[])double[]":[
        "(Double)-1.79769313486232E+308",
        "(Double)1.79769313486232E+308"
    ],
    "(Boolean[])bool[]":[
        "(Boolean)True",
        "(Boolean)False"
    ]
}

Web 서비스 수신:

JavaScript

{
    "(object)Dictionary":{
        "dk_int":"(number)1",
        "dk_str":"(string)dv2",
        "dk_bool":"(boolean)true"
    },
    "(object)Hashtable":{
        "hk_byte":"(number)255",
        "hk_null":null,
        "hk_array":[
            "(number)0",
            "(string)xy",
            "(boolean)false"
        ]
    },
    "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"
    ]
}

웹 서비스 -> Photon 서버

C#/.Net 에서 JavaScript/JSON 타입과 각각 대응하는 테이블입니다.
Here is a table that matches each JavaScript/JSON type to its equivalent one in 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 Service 전송:

JavaScript

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

Photon Server 수신:

JavaScript

{
    "(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)number":"-3.14"
    },
    "(Object[])array":[ 
        "(Object)xyz",
        "(Object)0",
        "(Object)True",
        "null",
        "null"
    ]
}

문제 해결

대시보드에 구성한 인증 URL이 HTTP 에러를 반환하는 경우, Photon 서버는 과부하를 피하기 위해 짧은 시간 동안 인증 호출을 일시 중지(백오프)합니다.
URL을 구성하거나 테스트할 때 이 “백오프” 시간을 고려하세요.

모범 사례

  • 인증 제공자에서 반환하는 결과에 특히 실패 시 읽기 쉬운 메시지를 포함하세요.
    이는 디버깅 시간을 크게 절약합니다.
  • 대시보드에서는 클라이언트에서 설정하면 안 되는 정적 키/값을 설정하세요.
    결과 쿼리 스트링에서 키 중복을 방지합니다.
  • 보안상의 이유로 인증 파라미터로 평문 비밀번호를 보내지 마세요.
  • 요청의 출처를 확인할 수 있도록 쿼리 스트링 파라미터는 가능하면 Photon 대시보드에서 설정하는 것이 좋습니다.
  • 파라미터 설정에는 AuthenticationValues의 메서드를 사용하고 AuthGetParameters에 직접 값을 대입하지 마세요.
    잘못된 쿼리 스트링을 방지할 수 있습니다.

유스 케이스 예제: 오래된 버전의 클라이언트 블록

커스텀 인증을 사용하면 구버전(또는 예상치 못한 버전)의 클라이언트에서 들어오는 연결을 거부하고, 사용자에게 업데이트를 요청하는 특정 에러를 반환할 수 있습니다.
이를 위해서는 커스텀 인증 요청에 버전을 전송해야 합니다. 쿼리 스트링 파라미터나 POST 데이터 중 원하는 방법을 사용할 수 있습니다.
아래 예에서는 쿼리 스트링 파라미터를 사용합니다:

커스텀 인증 URL이 https://example.com이라면 요청은 https://example.com?version={version} 형태로 전송됩니다.
인증 제공자 구현에서는 수신한 버전을 확인해 비교해야 합니다.
허용된 버전이면 { "ResultCode": 1 }을 반환하세요.
허용되지 않는 경우에는 ResultCode를 1이 아닌 사용자 정의 값으로(가능하면 메시지와 함께) 반환하세요.
예: { "ResultCode": 5, "Message": "Version not allowed." }.

Back to top