콘텐츠로 이동

앱 및 계정 인증

cTrader Open API 인증 프로세스는 OAuth 2.0 표준을 기반으로 합니다.

이 프레임워크는 리소스 소유자를 대신하여 서비스에 대한 제한된 액세스를 얻기 위해 리소스 소유자와 서비스 간의 승인 상호작용을 조율하거나, 타사 애플리케이션이 자체적으로 액세스를 얻을 수 있도록 허용합니다.

인증 흐름

OAuth 2.0 표준에 따라, 계정 인증에는 인증 코드와 액세스 토큰이 모두 필요합니다.

토큰 정의

인증 코드는 개별 cTID에 대해 발급되는 단기 토큰입니다. 만료 기간은 1분입니다. 액세스 토큰은 애플리케이션이 cTrader 백엔드와 메시지를 주고받을 수 있도록 하는 장기 토큰입니다. 이를 받은 후, 인증 코드는 빠르게 액세스 토큰으로 교환되어야 합니다; 이후 cTrader 백엔드로 보내는 모든 메시지는 받은 액세스 토큰으로 서명되어야 애플리케이션을 인증할 수 있습니다. 액세스 토큰의 만료 기간은 2,628,000초(약 30일)입니다. 만료된 후, 리프레시 토큰을 사용하여 새로운 액세스 토큰을 생성할 수 있습니다. 리프레시 토큰은 만료 기간이 없습니다.

일반적으로, 누군가가 처음으로 귀하의 애플리케이션을 사용하기 시작하는 과정은 다음과 같습니다:

1. 특정 cTID를 가진 사용자가 애플리케이션이 배포된 곳에 접근합니다; 사용자는 액션 버튼이나 이에 상응하는 것과 상호작용합니다.

2. 애플리케이션은 임베디드 브라우저(또는 다른 적절한 수단)를 사용하여 사용자를 특정 URI로 리디렉션합니다. 여기서 사용자는 앱에 필요한 권한을 부여합니다.

신뢰 확보

사용자를 이 URI로 리디렉션하기 전에, 사용자가 귀하의 앱을 신뢰하고 추가 권한을 제공해야 한다는 사실을 알고 있는지 확인하세요.

3. 사용자는 미리 정의된 리디렉션 URL로 전송됩니다. 이 URL에는 code 쿼리 매개변수의 값으로 액세스 토큰이 포함됩니다.

4. 사용자는 자발적으로 또는 자동으로 앱으로 다시 전송됩니다. 앱은 code 매개변수의 값을 알게 됩니다.

5. 앱은 인증 코드를 액세스 토큰으로 교환하기 위해 특정 엔드포인트에 요청을 보냅니다.

6. 앱은 accessToken 키의 값으로 액세스 토큰을 포함한 응답을 받습니다.

7. 앱은 clientIdclientSecret 필드를 포함한 ProtoOAApplicationAuthReq 메시지를 보냅니다.

8. 앱은 accessToken 필드를 포함한 ProtoOAGetAccountListByAccessTokenReq 메시지를 보냅니다. 응답(ProtoOAGetAccountListByAccessTokenRes)은 반복되는 ctidTraderAccountId 필드를 포함합니다.

9. 응답을 받은 후, 앱은 ctidTraderAccountIdaccessToken 필드를 포함한 ProtoOAAccountAuthReq 메시지를 보냅니다.

이후, 사용자는 ctidTraderAccountId가 8단계에서 제공된 ctidTraderAccountId와 일치하는 계정으로 인증되어야 합니다.

아래 다이어그램은 Open API 애플리케이션의 관점에서 기본 인증 흐름이 어떻게 작동하는지 설명합니다.

graph TB
  A([사용자를 특정 URI로 <br/> 리디렉션]) ==> B([인증 코드 <br/> 수신]);
  B ==> C([액세스 코드 획득을 위한 <br/> 요청 전송]);
  C ==> D([ProOAApplicationAuthReq <br/> 메시지 전송]);
  D ==> E([ProtoOAAccountAuthReq <br/> 메시지 전송]);

아래 하위 섹션에서는 인증 흐름의 주요 단계를 자세히 설명합니다.

리디렉션 URI 추가

이전에 보여진 것처럼, 사용자가 특정 리디렉션 URI로 전송된 후 귀하의 앱은 인증 코드를 얻어야 합니다. 이 URI는 code 쿼리 매개변수의 값으로 인증 코드를 포함할 것입니다.

새로운 리디렉션 URI를 추가하려면 다음을 수행하세요:

1. Open API 포털에서 애플리케이션 페이지를 엽니다.

2. 리디렉션 URL을 추가하려는 애플리케이션의 행에서 편집 버튼을 선택합니다.

3. 새로 나타난 화면에서 리디렉션 URI 섹션으로 스크롤합니다.

4. 사용 가능한 빈 텍스트 입력 필드에 리디렉션 URL을 추가합니다. 사용 가능한 필드가 표시되지 않으면 리디렉션 URI 추가 버튼을 선택합니다.

5. 페이지 하단의 저장 버튼을 선택하여 변경 사항을 적용합니다.

리디렉션 URI 관리

첫 번째 리디렉션 URI는 일반 사용자에게 액세스를 제공하기 전에 애플리케이션을 테스트할 수 있는 Playground 서비스를 위한 것입니다. 이는 프로덕션 애플리케이션에 사용되어서는 안 됩니다. 필요한 경우 무제한으로 리디렉션 URI를 추가할 수 있습니다.

인증 코드 획득

특정 cTID에 대한 인증 코드를 받으려면, 귀하의 앱은 사용자를 다음 URI로 보내야 합니다.

https://id.ctrader.com/my/settings/openapi/grantingaccess/?client_id={clientId}&redirect_uri={your_redirectURI}&scope={scope}&product=web

참고

이 단계에서 사용자는 귀하의 애플리케이션 또는 서비스가 자신의 거래 계정에 액세스할 수 있도록 허용해야 합니다. 결과적으로, 사용자에게 귀하의 애플리케이션 또는 서비스 사용이 권한, 데이터 저장 및 데이터 처리와 관련하여 무엇을 요구하는지에 대한 완전한 이해를 제공하는 것이 중요합니다.

이 URI에는 다음 쿼리 매개변수가 있습니다.

매개변수 필수? 정의
client_id 귀하의 Open API 애플리케이션의 고유 식별자입니다.
redirect_uri 애플리케이션 설정에 지정된 유효한 리디렉션 URI입니다.
scope 애플리케이션에 부여하고자 하는 권한의 범위입니다. 다음 값이 허용됩니다:
accounts. 읽기 전용 액세스가 부여됩니다. 애플리케이션은 계정 정보 및 통계에 액세스할 수 있지만, 거래 작업을 수행하는 것은 불가능합니다.
trading. 계정 정보 및 통계뿐만 아니라 모든 허용된 거래 작업에 대한 전체 액세스가 부여됩니다.
product 아니요 이 매개변수가 지정되고 web으로 설정된 경우, 권한 부여 화면에 헤더와 푸터가 없어 모바일 장치에서 더 나은 모습을 보입니다. 이 매개변수는 선택 사항이지만, web으로 설정하는 것을 권장합니다.

client_id를 보려면 애플리케이션 페이지로 이동하여 자격 증명 열에서 보기 버튼을 선택하세요.

사용자가 위의 URI로 이동하면(두 매개변수가 모두 유효한 경우), 로그인/가입 화면이 표시되어 cTID로 권한을 부여하도록 요청합니다. 그 후, 사용자에게 대화 상자가 표시되어 애플리케이션이 자신의 cTID에 연결된 하나 이상의 계정에 액세스할 수 있도록 허용할 수 있습니다.

사용자가 액세스 허용 버튼을 선택하면, 원래 지정한 redirect_uri로 리디렉션됩니다. 아래 예시와 같이, 권한 부여 코드가 이 URI에 쿼리 매개변수로 추가됩니다.

https://my-redirect-uri.com/?code=123ASDASffdg_as234_GCllkoq14adRB7_tvN

이 시점에서 애플리케이션은 code 매개변수의 값에 액세스하고 저장해야 합니다. 권한 부여 코드가 만료되기 전에 액세스 토큰으로 빠르게 교환해야 합니다.

추가 계정

사용자가 위에서 설명한 작업을 완료한 후 추가 계정을 생성하면, 이러한 계정은 애플리케이션 내에서 권한이 부여되지 않습니다. 대신, 사용자는 위에서 제공한 URL로 다시 리디렉션되어 마지막 로그인 이후 생성한 추가 계정에 애플리케이션이 액세스할 수 있도록 허용해야 합니다.

Google을 통한 인증

위의 URI가 WebView를 통해 열리고 사용자가 Google로 로그인을 선택하면, Google은 액세스 차단됨 페이지를 표시하여 사용자가 더 이상 진행할 수 없도록 합니다.

이를 방지하려면 페이지를 여는 장치의 user-agent 서명이 다음과 같이 보이는지 확인하세요.

'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'

액세스 토큰 획득

액세스 토큰을 얻으려면 다음 REST API 호출을 수행하세요.

상대 URL

요청 URL은 https://openapi.ctrader.com에 상대적입니다.

메소드 URL
GET apps/token

이 요청에는 다음 쿼리 매개변수가 포함됩니다.

매개변수 필수? 정의
grant_type 이 엔드포인트로 전송된 토큰의 유형입니다. 다음 값이 허용됩니다.
authorization_code. 권한 부여 코드가 엔드포인트로 전송되어 액세스 토큰으로 교환됩니다.
refresh_token. 리프레시 토큰이 엔드포인트로 전송되어 기존 액세스 토큰을 갱신합니다.
code 아니요 엔드포인트로 전송된 토큰입니다. 이 매개변수는 grant_type=authorization_code인 경우 필수입니다.
redirect_uri 아니요 애플리케이션 설정에 지정된 유효한 리디렉션 URI입니다. 이 매개변수는 grant_type=authorization_code인 경우 필수입니다.
client_id 귀하의 Open API 애플리케이션의 고유 식별자입니다.
client_secret 귀하의 Open API 애플리케이션에 할당된 시크릿 키입니다.

요청 예시는 아래에서 확인할 수 있습니다.

curl -X GET 'https://openapi.ctrader.com/apps/token?grant_type=authorization_code&code=0ssdgds98as9_QSF56FVC_22dfdf&redirect_uri=https://spotware.com&client_id=5430012&client_secret=012sds23dlkjQsd' -H 'Accept: application/json' -H 'Content-Type: application/json'

client_secret을 보려면 애플리케이션 페이지로 돌아가서 자격 증명 열에서 보기 버튼을 선택하세요.

요청이 성공적으로 완료되면, 다음 키가 포함된 응답을 받아야 합니다.

값 유형 정의
"accessToken" 문자열 애플리케이션이 cTrader Open API로 전송된 메시지를 인증하는 데 사용할 액세스 토큰입니다.
"tokenType" 문자열 액세스 토큰의 유형입니다(요청을 위해 REST 표준이 사용되므로 이 키의 값은 "bearer"여야 합니다).
"expiresIn" 정수 액세스 토큰이 만료되기 전에 경과해야 하는 초 수입니다(기본적으로 이 키의 값은 2628000입니다).
"refreshToken" 문자열 액세스 토큰이 만료된 후 이를 갱신하기 위해 사용해야 하는 리프레시 토큰입니다. 리프레시 토큰 자체는 만료 시간이 없습니다.
"errorCode" string/null 요청이 실패한 이유를 설명하는 오류 코드입니다. 성공적인 요청의 경우, 이 키의 값은 null이어야 합니다.
"description" string/null 실패한 요청 중 발생한 오류에 대한 설명입니다. 성공적인 요청의 경우, 이 키의 값은 null이어야 합니다.

성공적인 응답의 예는 아래 스니펫을 참조하세요.

{
    "accessToken":"mos8Bw3D4EG0fRPd4Eqq0JxaFT4zjd8e4YijNezh_ag",
    "tokenType":"bearer",
    "expiresIn":2628000,
    "refreshToken":"VCuafFhy81AFZjsWkbuEzdOhhRj5YTWz8fWUwHam7KM",
    "errorCode":null,
    "description":null,
}

필요한 ProtoBuf 메시지 보내기

유효한 액세스 코드를 받은 후, 애플리케이션은 ProtoOAApplicationAuthReq, ProtoOAGetAccountListByAccessTokenReqProtoOAAccountAuthReq 메시지를 보내야 합니다. 이는 다음과 같이 수행할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private static RepeatedField<ProtoOACtidTraderAccount> _ctidTraderAccounts;

List<IDisposable> _disposables = new();
_disposables.Add(_client.OfType<ProtoOAGetAccountListByAccessTokenRes>().Subscribe(OnAccountIdReceived));

private static void OnAccountIdReceived(ProtoOAGetAccountListByAccessTokenRes response)
{

    Console.WriteLine($"\nMessage Received:\n{response}");

    _ctidTraderAccounts = response.CtidTraderAccount;

    var accountAuthReq = new ProtoOAAccountAuthReq
    {
        CtidTraderAccountId = (long)_ctidTraderAccounts[0].CtidTraderAccountId,
        AccessToken = _token.AccessToken,
    };

    _client.SendMessage(accountAuthReq);

}

var _client = new OpenClient(host, ApiInfo.Port, TimeSpan.FromSeconds(10), useWebSocket: false);
var applicationAuthReq = new ProtoOAApplicationAuthReq
{
    ClientId = {your_client_id}, // Add your client ID here
    ClientSecret = {your_app_secret}, // Add your app secret here
};
await _client.SendMessage(applicationAuthReq);
await Task.Delay(5000);
var getAccountListByAccessToken = new ProtoOAGetAccountListByAccessTokenReq 
{
    AccessToken = {access_token} // Add your access token here
}
var accountAuthReq = new ProtoOAAccountAuthReq 
{
    ctidTraderAccountId = {account_id}, // Add the ctidTraderAccountId of the account you want to authorize here
    AccessToken = {access_token} // Add your access token here
};
await _client.SendMessage(accountAuthReq);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
currentAccountId = None
client = Client(EndPoints.PROTOBUF_LIVE_HOST if hostType.lower() == "live" else EndPoints.PROTOBUF_DEMO_HOST, EndPoints.PROTOBUF_PORT, TcpProtocol)

def connected(client): # Callback for client connection
    print("\nConnected")
    request = ProtoOAApplicationAuthReq()
    request.clientId = appClientId
    request.clientSecret = appClientSecret
    deferred = client.send(request)



def disconnected(client, reason): # Callback for client disconnection
    print("\nDisconnected: ", reason)

def onMessageReceived(client, message): # Callback for receiving all messages
    global currentAccountId
    if message.payloadType in [ProtoOASubscribeSpotsRes().payloadType, ProtoOAAccountLogoutRes().payloadType, ProtoHeartbeatEvent().payloadType]:
        return
    elif message.payloadType == ProtoOAApplicationAuthRes().payloadType:
        print("API Application authorized\n")
        if currentAccountId is not None:
            sendProtoOAAccountAuthReq()
            return
        accountListReq = ProtoOAGetAccountListByAccessTokenReq()
        accountListReq.accessToken = accessToken
        deferred = client.send(accountListReq)
    elif message.payloadType == ProtoOAGetAccountListByAccessTokenRes().payloadType:
        protoOAGetAccountListByAccessTokenRes = Protobuf.extract(message)
        currentAccountId = int(protoOAGetAccountListByAccessTokenRes.ctidTraderAccount[0].ctidTraderAccountId)
        accountAuthReq = ProtoOAAccountAuthReq()
        accountAuthReq.ctidTraderAccountId = currentAccountId
        accountAuthReq.accessToken = accessToken
        deferred = client.send(accountAuthReq)
    elif message.payloadType == ProtoOAAccountAuthRes().payloadType:
        protoOAAccountAuthRes = Protobuf.extract(message)
        print(f"Account {protoOAAccountAuthRes.ctidTraderAccountId} has been authorized\n")
        print("This acccount will be used for all future requests\n")
    else:
        print("Message received: \n", Protobuf.extract(message))
    reactor.callLater(3, callable=executeUserCommand)

def onError(failure): # Call back for errors
    print("Message Error: ", failure)
    reactor.callLater(3, callable=executeUserCommand)

액세스 토큰 갱신

액세스 토큰이 만료된 후, 이전에 받은 refreshToken을 사용하여 이를 갱신해야 합니다. 이 작업은 두 가지 다른 방법으로 수행할 수 있습니다.

HTTP 요청 보내기

새로운 액세스 토큰(그리고 새로운 리프레시 토큰)을 얻기 위해, 위에서 다룬 동일한 HTTP 요청을 수행할 수 있습니다. 아래 예제에서 보여주듯이, grant_type 쿼리 매개변수를 refresh_token으로 설정해야 합니다.

curl -X POST 'https://openapi.ctrader.com/apps/token?grant_type=refresh_token&refresh_token=asdXCVCVAS_218kjashf_asdasd2ASD_223x&client_id=5430012&client_secret=012sds23dlkjQsd' -H 'Accept: application/json' -H 'Content-Type: application/json'

새로운 accessTokenrefreshToken 키 값을 포함한 응답을 받게 됩니다. 이 새로운 값들은 안전하게 사용할 수 있으며, 동일한 키의 이전 값들은 자동으로 무효화됩니다.

Protobuf 메시지 보내기

또는 ProtoOARefreshTokenReq를 보내고 새로운 유효한 토큰을 포함한 ProtoOARefreshTokenRes 메시지를 받을 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
_disposables.Add(_client.OfType<ProtoOARefreshTokenRes>().Subscribe(OnRefreshTokenResponse));
private static void OnRefreshTokenResponse(ProtoOARefreshTokenRes response) 
{
    _token = new Token 
    {
        AccessToken = response.AccessToken,
        RefreshToken = response.RefreshToken,
        ExpiresIn = DateTimeOffset.FromUnixTimeMilliseconds(response.ExpiresIn),
        TokenType = response.TokenType,
    }
}
var refreshTokenReq = new ProtoOARefreshTokenReq
    {
        refreshToken = {your_refresh_token}, // Add your refresh token here
    };
await _client.SendMessage(applicationAuthReq);
1
newToken = auth.refreshToken("refresh_Token")

Playground 사용

Playground는 자신의 cTID에 대한 액세스 토큰을 빠르게 얻을 수 있는 수단을 제공하여, 앱을 대상 사용자에게 배포하기 전에 빠르고 쉽게 개발하고 테스트할 수 있게 합니다.

Playground에 접근하려면 Applications 페이지로 이동하여 Playground 버튼을 선택하세요. 새로 나타난 창에서 애플리케이션의 범위를 선택하고 Get token 버튼을 클릭하세요.

그 후, 다음 페이지가 나타납니다.

자신의 cTID로 인증된 상태에서 API에 메시지를 보내기 위해 액세스 토큰과 리프레시 토큰을 사용할 수 있습니다.