Set up OIDC as an identity provider
Overview
The purpose of this guide is to help you integrate OpenID Connect (OIDC) with AccelByte Gaming Services (AGS). We support three OIDC authentication types: Authorization Code, ID Token, Access Token.
You may need to set up additional identity provider features which are not listed here, depending on your game. For more information about setting up OIDC services, we recommend reviewing your identity provider's documentation.
This guide is intended for public use and contains limited information due to confidentiality. We recommend you refer to the full confidential guide first. To request a copy of the confidential guide, contact your AccelByte Technical Producer.
Goals
- Enable the OIDC authentication method for your game with AccelByte's Game SDK.
- Enable the OIDC authentication method for your publisher website with AGS's Player Portal.
Prerequisites
Web login integration
- An identity provider that supports OIDC.
- An AccelByte Admin Portal account to set up authentication and manage permissions.
- A publisher Namespace for your Player Portal and Launcher
- A Player Portal.
In-game login integration
- An identity provider that supports OIDC.
- An AGS Admin Portal account to set up authentication and manage permissions.
- A game Namespace.
- A Unity or Unreal game project into which you imported AccelByte's Game SDK.
- The latest version of the AccelByte Unity SDK or Unreal SDK.
- Familiarity with AGS Identity and Access Management (IAM) Clients.
Authorization Code type
Authorization Code type supports authentication by both using the standard Authorization Code Flow and validating ID token via the JSON Web Key Set (JWKS). Authorization Code is webflow-based authentication. The system redirects you to your identity provider login page and back to the AccelByte portal with access.
Set up your IdP
Follow these steps to set up identity provider (IdP) OIDC logins in your game. This will allow your players to sign in to the Admin Portal, Player Portal, and Launcher using their accounts.
We use Okta OIDC as an example to set up the OIDC Player Portal and Admin Portal login. For Authorization Code, you need these items to integrate your identity provider OIDC with AGS:
- JWKS URL
- Issuer
- Client ID
- Client Secret
- Authorization Request URL
- Token Endpoint URL
As an example, we will use Okta as our identity provider. You can use another IdP that supports OIDC.
Create an Okta application
Follow Okta's Create OIDC app integrations guide to create an Okta Application under your Okta account.
Keep a copy of the client ID and client secret that you generate in this step. You will need it when you set up the web login.
Get your Okta OIDC configuration
Use this URL to open your app openid-configuration: https://<YOUR_OKTA_URL>.okta.com/oauth2/default/.well-known/openid-configuration
.
You will need to copy the following items to configure OIDC Authorization Code type login:
- JWKS URL
- Issuer
- Authorization Request URL
- Token Endpoint URL
Set up web login
To set up OIDC logins in the Player Portal and Admin Portal and to enable your players to sign in to the Player Portal using their IdP accounts, follow these steps:
In the AGS Admin portal, go to your publisher namespace.
On the sidebar menu, go to Game Setup > 3rd Party Configuration > Auth & Account Linking.
On the Login Methods page, click on the + Add New button.
At the bottom of the login method options, click on + Create OIDC.
The Create OpenID Connect (OIDC) form displays. Fill in the required information. In this example, we use the Authorization Code type.
- Platform Name: Type in a representative name, e.g,
oidcokta
. - Platform ID: Fill in the field with the platform ID that you use as
Valid RedirectURI
on the Okta App, e.g.,oidcokta
. - Authentication type: Select Authorization Code.
- JWKS URL: Use the jwks_url on your Okta
openid
configuration URL. - Issuer: Use the issuer on your Okta
openid
configuration URL. - Client ID: Use the Okta Client ID that you obtained from the Create an Okta application step.
- Client Secret: Use your Okta Client Secret that you obtained from the Create an OKTA application step.
- Authorization Request URL: Use the authorization_endpoint on your Okta
openid
configuration URL. - Token Endpoint URL: Use the token_endpoint on your Okta
openid
configuration URL. - Clients: Depending on your usage, you can use OIDC login for the Player Portal, Launcher, Admin Portal, and In-Game login by setting those clients on this OIDC. For example, if you want to integrate OIDC Login with the Player Portal and Admin Portal, you need to add the Player Portal Website IAM Client and Admin Portal Website IAM Client here.
- Scopes (Optional): By default, this is set to openid email profile.
- Platform Name: Type in a representative name, e.g,
Click Next.
On the Token Claims Mapping form, map the user identity in the ID Token to enable the system to parse it into the AccelByte account.
Click Next.
(Optional) In the Domains section, you can register your domain and attach a role to the user.
To skip this step, click Create.
To register your domain, click + Register Domain. Then, fill in the following fields on the Register Domain form. After you fill in the fields, click Save, then click Create.
Domain Name: Type in the name of the domain. In this example, we use
accelbyte.net
.Clients: The system grants the user default roles when they first login to the client. In this example we want to grant the Admin role when a user logs in using OIDC on the Admin Portal, so we set it to Admin Portal Website IAM Client.
Default Role: The role you want to grant the user.
Default Namespace: if the default role is not a global role, specify its targeted Namespace.
Register Domain is useful if you want to automatically assign roles to the user under a specified domain. For example, the OIDC login is used for Admin Portal Login. You want to automatically grant a role to the user under
<your-domain>
after they log in using the OIDC Login.
The system redirects you to the details page of the OIDC you created. Click Activate to activate the OIDC.
User login from the Player Portal using OIDC
Ensure you set up an OIDC Login Method on Publisher Namespace and set Player Portal Website Client as the OIDC Client.
Go to the Player Portal and click Login.
Click More Login Options.
Click the login option with the OIDC you set up. In this example, we use Okta.
The system redirects you to the Okta Login Page. Type your credentials and log in.
noteThe system asks players to link their OIDC account with an existing or new AccelByte account. AccelByte is working to make this configurable, enabling players to log in to the Player Portal directly with a headless account.
User login from Admin Portal using OIDC
Ensure you set up the OIDC Login Method on Publisher Namespace and set the Admin Portal Website Client as an OIDC Client.
To access the Admin Portal you need to attach an Admin Role to the user. If you already registered your domain as an OIDC Login Method and attached admin roles to it, the system grants every OIDC user under your registered domain that role.
Go to the Admin Portal. The system redirects you to the login page. Click on the OIDC logo. If there's more than one, hover over the button to ensure you use the correct OIDC Login.
The system redirects you to the Okta Login page. Use your credentials to log in.
The system redirects you to the Admin Portal.
Your access to the portal is based on your role. For example, if the role you assigned is Admin Portal Read Only for the AccelByte namespace, you can only access the features that role allows.
ID token type
ID Token type support authentication by validating ID Token via the JWKS. ID Token is for in-game-flow based authentication, You need to generate the ID Token from your Identity Provider. You can exchange it for an AccelByte access token.
Set up your IdP
Use the following steps to set up your identity provider (IdP) OIDC logins in your game. This will allow your players to sign in to your game using their accounts.
You need to handle the authorization from your IdP to get an ID Token. You need the following items to integrate your IdP OIDC with AGS:
- JWKS URL
- Issuer
- Client ID
As an example, we use Facebook as our IdP. You can use another IdP that supports OIDC.
Create a Facebook application
Follow Facebook's Create an App guide to create a Facebook application under your Facebook Developer Account.
Add a Facebook login product to your app
Follow the Facebook Login for the Web with the JavaScript SDK guide to add a Facebook Login Product to your app.
Set up in-game login
Follow these steps to set up OIDC logins in-game. We use Facebook OIDC as an example. This enables your players to sign in to your game using their identity provider accounts.
In the AGS Admin portal, go to your game namespace.
On the sidebar menu, go to Game Setup > 3rd Party Configuration > Auth & Account Linking.
On the Login Methods page, click on the + Add New button.
At the bottom of the login method options, click on + Create OIDC.
On the Create OpenID Connect (OIDC) form, fill in the required fields. In this example, we use ID Token as the authentication type.
- Platform Name: Type in the name for the login method, e.g.,
oidcfacebook
. - Platform ID: Type in the ID for the login method, e.g.,
oidcfacebook
. - Authentication type: Select ID Token.
- JWKS URL: As this example uses Facebook OIDC, use https://www.facebook.com/.well-known/oauth/openid/jwks/.
- Issuer: As this example uses Facebook OIDC, use
[https://www.facebook.com](https://www.facebook.com)
.
- Platform Name: Type in the name for the login method, e.g.,
Click Next.
Map the user identity in the ID token to enable the system to parse it into the AccelByte account. You can learn more about token claims in Claims.
The system redirects you to the details page of the OIDC you created. Click Activate to activate the OIDC.
Access Token type
Access Token type supports authentication by validating an access token via the UserInfo
API Endpoint. Access Token type is for custom needs when the IdP can't give you with an ID Token but has a User Info endpoint that you can use with its access token.
Set up your IdP
Follow these steps to set up your IdP OIDC logins in your game. This will allow your players to sign in to your game using their accounts.
To use the Access Token type, you need these items to integrate your IdP OIDC with AGS:
- User Info Endpoint URL
- User Info Endpoint HTTP method
As an example, we use Okta as our IdP. You can use another IdP that supports OIDC.
Create an Okta application
Follow Okta's Create OIDC app integrations guide to create an Okta application.
Set the sign-in redirect URIs for your OKTA application using these URLs:
<BaseURL>/iam/v3/platforms/<PlatformId>/authenticate
<BaseURL>/iam/v3/public/namespaces/<namespace>/users/me/platforms/<platformId>/web/link/establish
BaseURL
is your domain address. For example,https://development.accelbyte.io
.Namespace
is your publisher or studio namespace. For example,accelbyte
.PlatformId
is your OIDCPlatformId
that you will set on the AGS Admin Portal.
Set up in-game login
To set up in-game OIDC logins and to enable your players to sign in using their IdP accounts, follow these steps.
In the AGS Admin portal, go to your game namespace.
On the sidebar menu, go to Game Setup > 3rd Party Configuration > Auth & Account Linking.
On the Login Methods page, click on the + Add New button.
At the bottom of the login method options, click on + Create OIDC.
On the Create OpenID Connect (OIDC) for, fill in the required information. In this example (Okta), we use Access Token as the authentication type.
- Platform Name: Type in a name for the OIDC, e.g.,
oidc okta access token
. - Platform ID: Type in the ID for the OIDC,
oktaaccesstoken
. - Authentication type: Select Access Token.
- UserInfo Endpoint: Use your Okta application user info endpoint, e.g.,
https://YOUR_OKTA_URL.okta.com/oauth2/v1/userinfo
. - HTTP Method: Enter the corresponding HTTP method used by the IdP, e.g.,
GET
.
- Platform Name: Type in a name for the OIDC, e.g.,
Click Next.
Map the user identity in the User Info endpoint response to enable the system to parse it into the AccelByte account.
The system redirects you to the details page of the OIDC you created. Click Activate to activate the OIDC.
Create an IAM client for OIDC
An IAM client is a representation of the game client that you want to release on your target platform.
If you already have an IAM Client for your game on a specific SDK Platform (e.g., Xbox, Steam, or Playstation), you don't have to create a new IAM Client. Since an IdP is not a platform on which to build games, you can use an existing IAM Client. To learn more about IAM Clients, see the Manage Access control for applications article.
In-game login
The setup for each game engine is different. Follow the steps applicable to your game engine.
- Unreal Engine
- Unity Engine
Unreal Engine in-game login integration
To seamlessly integrate your game with the AccelByte SDK for in-game sign-in and enable your players to access games using OIDC credentials, an approach is taken due to IdP support for Unreal Engine 4. The implementation needs web login.
Unreal Engine preparation and configuration
Adding dependency
Start by incorporating the AccelbyteUe4Sdk
public dependency modules into your build.cs
file. This linkage facilitates the integration of the AccelByte SDK Plugin into your Unreal Engine project.
public ABThirdPartyLogin(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "AccelByteUe4Sdk", "HTTP"});
}
Since Unreal Engine lacks support for URI redirection from external browsers, you must utilize an in-game browser within Unreal itself. This requires adding private dependency modules to your build.cs
file as follows:
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore", "UMG", "WebBrowser", "WebBrowserWidget" });
Add AccelbyteUe4Sdk
in <YourProject>.Target.cs
.
public ABThirdPartyLoginTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V2;
ExtraModuleNames.AddRange( new string[] { "ABThirdPartyLogin", "AccelByteUe4Sdk" } );
}
And <YourProjectEditor>.Target.cs
public ABThirdPartyLoginEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V2;
ExtraModuleNames.AddRange( new string[] { "ABThirdPartyLogin", "AccelByteUe4Sdk" } );
}
UE project settings for OIDC login
Add your AccelByte credentials in your DefaultEngine.ini
file.
[/Script/AccelByteUe4Sdk.AccelByteSettings]
ClientId=<Your Client_Id>
ClientSecret=<Your Client_Secret>
Namespace=<Your Namespace>
PublisherNamespace=<Your Publisher Namespace>
RedirectURI="http://127.0.0.1"
BaseUrl="https://prod.gamingservices.accelbyte.io"
IamServerUrl="https://prod.gamingservices.accelbyte.io/iam"
PlatformServerUrl="https://prod.gamingservices.accelbyte.io/platform"
Sample code implementation
The subsequent step is to implement the OIDC authentication method for your game using the provided sample code. Begin by creating a C++ class; for instance, the following code will utilize an actor class.LoginActor.h
.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SWebBrowser.h"
#include "HttpModule.h"
#include "Core/AccelByteRegistry.h"
#include "Api/AccelByteUserApi.h"
#include "Core/AccelByteApiClient.h"
#include "Core/AccelByteMultiRegistry.h"
#include "Core/AccelByteMultiRegistry.h"
#include "Core/AccelByteError.h"
#include "LoginActor.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FActionInfo, FString, Message);
UCLASS()
class ABTHIRDPARTYTEST_API ALoginActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ALoginActor();
UPROPERTY(BlueprintAssignable)
FActionInfo OnActionInfoUpdated;
private:
TSharedPtr<SWebBrowser> SlateBrowserWidget;
FHttpModule* HttpModule;
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString ClientID;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString ClientSecret;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString RedirectURI;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString AuthorizationUrl;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString TokenUrl;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString ServerURL;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString ResponseType;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OIDC Settings")
FString OIDCScope;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Status")
FString LoginStatus;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Status")
bool bIsLoggedIn;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Status")
FString AccessToken;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OIDC Status")
FString IdToken;
public:
UFUNCTION(BlueprintCallable, Category = "OIDC")
void ExecBroadcast();
void HandleUrlChanged(const FText& Text);
void OnAccessTokenReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
void ParseAndHandleTokens(const FString& ResponseBody);
void LoginWithOtherPlatformId(const FString& PlatformId, const FString& Token, const FVoidHandler& OnSuccessDelegate);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
Obtain an ID Token or Access Token
Obtaining an ID Token or Access Token in your application is split into two scenarios:obtaining tokens using the Authorization Code and handling ID Tokens directly.
Obtain token using the Authorization Code
If your application requires the Authorization Code as the initial step to acquire the ID Token or Access Token and doesn't support the Implicit (Hybrid) Grant Type, follow these steps.
- Initialization and configuration: Ensure your application is properly configured with the required credentials, URLs, and scopes.
- Web-based authorization: Initiate the authorization process by constructing the Authorization URL with necessary parameters, including
response_type
,client_id
,redirect_uri
,scope
, and more. Launch a web browser widget to present the authorization page to the user. - URL change handling: Implement a URL change handler to capture the URL changes in the web browser widget. Detect if the URL contains an Authorization Code parameter.
- Token exchange: If an Authorization Code is detected, construct a request to exchange the code for an access token and possibly an ID token. Send a POST request to the Token URL with the required form data (e.g.,
code
,client_id
,client_secret
,redirect_uri
, andgrant_type
). - Token parsing and handling: Parse the JSON response from the token request. If the response contains an
access_token
field, extract the access token and ID token (if applicable). Use these tokens as needed for further operations. - Log in to AGS: If tokens are obtained, attempt to log in to AGS using the
LoginWithOtherPlatformId
method. Handle the success and error scenarios accordingly. - Cleanup: Hide or close the web browser widget as needed.
Handle ID tokens directly
In situations where the response URL directly contains a Platform ID Token
fragment, follow these steps:
- Initialization and configuration: Ensure your application is properly configured with the required credentials, URLs, and scopes.
- Web-based authorization: Initiate the authorization process by constructing the Authorization URL with parameters. Launch a web browser widget.
- URL change handling: Detect if the URL contains a
ID Token
fragment. - Token handling: If a
ID Token
fragment is detected, handle the token directly as a string. Parse any necessary information from the token. - Log in to AGS: Attempt to log in to AGS using the
LoginWithOtherPlatformId
method. Handle the success and error scenarios. - Cleanup: Hide or close the web browser widget.
After following these steps and utilizing the provided code snippets, you can efficiently obtain ID Tokens or Access Tokens in your application based on your specific requirements.
Code example
LoginActor.cpp
ALoginActor::ALoginActor()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = false;
HttpModule = &FHttpModule::Get();
// Initialize variables with your desired values.
ClientID = TEXT("Your_IdentityProvider_ClientId");
ClientSecret = TEXT("Your_IdentityProvider_ClientSecret");// Used to exchange the authorization_code into idtoken or access token using /oauth2/default/v1/token endpoint.
RedirectURI = TEXT("http://localhost:8080/login/callback");
AuthorizationUrl = TEXT("https://{Your_Identity Provider_BaseUrl}/oauth2/default/v1/authorize");
TokenUrl = TEXT("https://{Your_Identity Provider_BaseUrl}/oauth2/default/v1/token");
OIDCScope = TEXT("openid profile email phone address");
}
// Called when the game starts or when spawned.
void ALoginActor::BeginPlay()
{
Super::BeginPlay();
}
void ALoginActor::ExecBroadcast()
{
// Please be advised that this can be different. Sync it with your own requested form data.
FString AuthorizationURL = AuthorizationUrl + "?response_type=" + ResponseType + "&client_id=" + ClientID +
"&redirect_uri=" + RedirectURI + "&scope=" + OIDCScope + "&state=foo" + "&nonce=nonce";
OnActionInfoUpdated.Broadcast(FString::Printf(TEXT("Trying To Login First")));
TSharedRef<SVerticalBox> MainContent = SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
// WebBrowserWidget will launch in-game browser.
SAssignNew(SlateBrowserWidget, SWebBrowser)
.InitialURL(AuthorizationURL)
.ShowControls(false)
.ShowAddressBar(false)
.ShowErrorMessage(true)
.SupportsTransparency(false)
.SupportsThumbMouseButtonNavigation(false)
.ShowInitialThrobber(true)
.PopupMenuMethod(TOptional<EPopupMethod>())
.ViewportSize(FVector2D::ZeroVector)
.OnUrlChanged_Lambda([this](const FText& Text)
{
FString UrlString = Text.ToString();
UE_LOG(LogTemp, Warning, TEXT("WebBrowser URL changed: %s"), *UrlString);
FText FullUrlText = FText::FromString(UrlString); // Assuming UrlString is the full URL
HandleUrlChanged(FullUrlText);
})];
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->AddViewportWidgetContent(SlateBrowserWidget.ToSharedRef());
}
}
void ALoginActor::HandleUrlChanged(const FText& Text)
{
UE_LOG(LogTemp, Warning, TEXT("The link has changed: %s"), *Text.ToString());
FString UrlString = Text.ToString();
// Check if the URL contains a query parameter or a fragment.
FString AuthorizationCode;
FString PlatformIdToken;
if (UrlString.Contains(TEXT("?code=")))
{
AuthorizationCode = FPlatformHttp::UrlDecode(UrlString.RightChop(UrlString.Find(TEXT("?code=")) + 6));
}
else if (UrlString.Contains(TEXT("#id_token=")))
{
PlatformIdToken = FPlatformHttp::UrlDecode(UrlString.RightChop(UrlString.Find(TEXT("#id_token=")) + 10));
}
// Process the authorization code or ID token.
if (!AuthorizationCode.IsEmpty())
{
// Build the request form data.
FString FormData = TEXT("code=") + AuthorizationCode
+ TEXT("&client_id=") + ClientID
+ TEXT("&client_secret=") + ClientSecret
+ TEXT("&redirect_uri=") + RedirectURI
+ TEXT("&grant_type=authorization_code");
// Make a POST request to exchange the authorization code for an access token or ID Token.
TSharedRef<IHttpRequest> HttpRequest = HttpModule->CreateRequest();
HttpRequest->SetURL(TokenUrl);
HttpRequest->SetVerb(TEXT("POST"));
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));
HttpRequest->SetContentAsString(FormData);
HttpRequest->OnProcessRequestComplete().BindUObject(this, &ALoginActor::OnAccessTokenReceived);
HttpRequest->ProcessRequest();
// Hide or close the SWebBrowser widget.
if (SlateBrowserWidget.IsValid())
{
SlateBrowserWidget->SetVisibility(EVisibility::Collapsed);
}
}
else if (!PlatformIdToken.IsEmpty())
{
OnActionInfoUpdated.Broadcast(FString::Printf(TEXT("Platform ID Token : %s"), *PlatformIdToken));
// Call the common handling function for tokens.
ParseAndHandleTokens(PlatformIdToken);
// Hide or close the SWebBrowser widget.
if (SlateBrowserWidget.IsValid())
{
SlateBrowserWidget->SetVisibility(EVisibility::Collapsed);
}
}
}
void ALoginActor::OnAccessTokenReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (bWasSuccessful && Response.IsValid())
{
FString ResponseBody = Response->GetContentAsString();
// Parse the JSON response and handle tokens.
ParseAndHandleTokens(ResponseBody);
}
}
void ALoginActor::ParseAndHandleTokens(const FString& ResponseBody)
{
// Remove any line breaks and extra spaces from the response body.
FString CleanedResponseBody = ResponseBody.Replace(TEXT("\n"), TEXT("")).Replace(TEXT(" "), TEXT(""));
UE_LOG(LogTemp, Warning, TEXT("Response Body: %s"), *ResponseBody);
const FVoidHandler OnLoginSuccessDelegate = FVoidHandler::CreateLambda([=]()
{
FAccountUserData AccountUserData = AccelByte::FRegistry::Credentials.GetAccountUserData();
FString AccountUserDataString;
FJsonObjectConverter::UStructToJsonObjectString(AccountUserData, AccountUserDataString);
FString Message = FString::Printf(TEXT("Login to AB Service Success : %s"), *AccountUserDataString);
OnActionInfoUpdated.Broadcast(Message);
UE_LOG(LogTemp, Warning, TEXT("Login to AB Service Success: %s"), *AccountUserDataString);
});
if (CleanedResponseBody.Contains(TEXT("access_token")))
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(CleanedResponseBody);
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
AccessToken = JsonObject->GetStringField(TEXT("access_token"));
IdToken = JsonObject->GetStringField(TEXT("id_token"));
// Use the access token and ID token as needed.
UE_LOG(LogTemp, Warning, TEXT("Access Token: %s"), *AccessToken);
UE_LOG(LogTemp, Warning, TEXT("ID Token: %s"), *IdToken);
OnActionInfoUpdated.Broadcast(FString::Printf(TEXT("Platform Access Token : %s"), *AccessToken));
OnActionInfoUpdated.Broadcast(FString::Printf(TEXT("Platform ID Token : %s"), *IdToken));
// Try to login into AGS Endpoint
if (!AccessToken.IsEmpty() || !IdToken.IsEmpty())
{
LoginWithOtherPlatformId("oidctest", IdToken, OnLoginSuccessDelegate);
}
}
else
{
FString ErrorMsg = Reader->GetErrorMessage();
UE_LOG(LogTemp, Error, TEXT("Failed to parse JSON response. Error: %s"), *ErrorMsg);
}
}
else if (!CleanedResponseBody.IsEmpty()) // Check if the response body is not empty.
{
// Handle ID token directly as a string.
IdToken = CleanedResponseBody;
// Use the ID token as needed.
UE_LOG(LogTemp, Warning, TEXT("ID Token: %s"), *IdToken);
OnActionInfoUpdated.Broadcast(FString::Printf(TEXT("Platform ID Token : %s"), *IdToken));
// Try to log in to AGS Endpoint.
LoginWithOtherPlatformId("oidctest", IdToken, OnLoginSuccessDelegate);
}
}
void ALoginActor::LoginWithOtherPlatformId(const FString& PlatformId, const FString& Token, const FVoidHandler& OnSuccessDelegate)
{
OnActionInfoUpdated.Broadcast(FString::Printf(TEXT("Login to AGS")));
FAccountUserData AccountUserData = AccelByte::FRegistry::Credentials.GetAccountUserData();
AccelByte::FRegistry::User.LoginWithOtherPlatformId(PlatformId, Token, OnSuccessDelegate, LoginTestFErrorOAuthHandler);
UE_LOG(LogTemp, Warning, TEXT("Request LoginWithOtherPlatform"));
OnActionInfoUpdated.Broadcast(TEXT("Request LoginWithOtherPlatform"));
}
// Called every frame
void ALoginActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
The provided code serves as an illustrative example and guide only. While it offers insights into the process of obtaining tokens, it's imperative to recognize that real-world implementations might involve additional considerations and limitations. Ensure rigorous error handling, follow best security practices, and conduct comprehensive testing to establish a resilient authentication and token retrieval system tailored to your application's specific requirements.
Sample code login log
PIE: Server logged in
PIE: Play in editor total start time 0.317 seconds.
LogViewport: Scene viewport resized to 1282x730, mode Windowed.
LogAccelByte: Warning: [Compatibility] Version mismatch: Expected "group" version "2.11.0"-"2.11.0", found "2.18.1"
LogTemp: Warning: The link has changed: https://dev-23268164.okta.com/oauth2/default/v1/authorize?response_type=code&client_id=0oaaomiaanDOioaIe5d7&redirect_uri=http://localhost:8080/login/callback&scope=openid%20profile%20email%20phone%20address&state=foo
LogTemp: Warning: The link has changed: https://dev-23268164.okta.com/oauth2/default/v1/authorize?response_type=code&client_id=0oaaomiaanDOioaIe5d7&redirect_uri=http://localhost:8080/login/callback&scope=openid%20profile%20email%20phone%20address&state=foo
LogTemp: Warning: The link has changed: https://dev-23268164.okta.com/signin/refresh-auth-state/00JnYaV0WU7hlAu9AXJSC-GtLmPAUwtfEpBP_SHCxO
LogTemp: Warning: The link has changed: https://dev-23268164.okta.com/
LogTemp: Warning: The link has changed: http://localhost:8080/login/callback?code=cnV2E9gIfouXrtaQ9rG347W2o9q7yZ3COO5H3-EfTYI&state=foo
LogTemp: Warning: Access token response: {"token_type":"Bearer","expires_in":3600,"access_token":"xxxxxx","scope":"openid profile address phone email","id_token":"xxxxxx"}
LogTemp: Warning: Access Token: xxxxxx
LogTemp: Warning: ID Token: xxxxxx
LogTemp: Warning: Request LoginWithOtherPlatform
LogTemp: Warning: Login to AB Service Success: {
"authType": "EMAILPASSWD",
"bans": [],
"country": "ID",
"createdAt": "2023-08-30T01:58:08.038119Z",
"dateOfBirth": "",
"displayName": "Ken",
"emailVerified": false,
"enabled": true,
"lastEnabledChangedTime": "0001-01-01T00:00:00Z",
"loginId": "",
"namespace": "sdktest",
"permissions": [],
"phoneVerified": false,
"platformId": "",
"platformUserId": "",
"roles": [
"2251438839e948d783ec0e5281daf05b"
],
"userId": "415271c4ef874a99aa81aa6d0a999e00",
"username": "",
"emailAddress": "",
"avatarUrl": "",
"deletionDate": "",
"deletionStatus": false,
"lastDateOfBirthChangedTime": "0001.01.01-00.00.00",
"namespaceRoles": [
{
"namespace": "*",
"roleId": "2251438839e948d783ec0e5281daf05b"
}
],
"newEmailAddress": "",
"oldEmailAddress": "",
"phoneNumber": "",
"platformAvatarUrl": "",
"platformDisplayName": ""
}
Sample code testing
The provided code has been successfully tested using Okta in the Unreal Engine editor. After clicking the sign-in button on Okta login, you will be automatically logged in.
Unity in-game login integration
You can integrate your OIDC to sign in with AccelByte SDK to enable your players to log in to games using OIDC credentials.
Preparation and configuration
Unity project settings for AGS
Before the AccelByte SDK can run properly in your project, you need to fill in the values you created previously in the AccelByte Admin Portal and follow the steps below:
Create a JSON file and use
AccelByteSDKConfig.json
as its file name.Copy the
AccelByteSDKConfig.json
file and add it to your Unity project in theAssets/Resource
directory.Fill in the
AccelByteSDKConfig.json
file using your game's information. Here is an example of the JSON file:{
"Default": {
"Namespace": "<Your Game Namespace>",
"UsePlayerPrefs": true,//It will use Player Preferences
"EnableDebugLog": true,//Enable Debug Logging
"DebugLogFilter": "Log",//Type of Debug Log
"BaseUrl": "https://prod.gamingservices.accelbyte.io",
"RedirectUri": "http://127.0.0.1",
"AppId": "<Your AppId>",
"PublisherNamespace": "<Your Publisher Namespace>"
}
}Create a JSON file and use
AccelByteServerSDKConfig.json
as its file name. Then, add it to your Unity project in theAssets/Resource
directory.In the
AccelByteServerSDKConfig.json
file, add the following code to the to configure the game server. Here is an example of the JSON file:{
"Default": {
"Namespace": "<Your Game Namespace>",
"BaseUrl": "https://prod.gamingservices.accelbyte.io",
"RedirectUri": "http://127.0.0.1"
}
}Create two JSON files and use the following file names:
AccelByteSDKOAuthConfig.json
AccelByteServerSDKOAuthConfig.json
Then, add both files to your Unity project in the
Assets/Resources
directory. The contents of these JSON files should be:{
"Default": {
"ClientId": "<Your IAM Client ID>",
"ClientSecret": "<Your IAM Client Secret>"
}
}
Sample code implementation
The next step covers how to implement the OIDC authentication method for your game with sample code.
You need to create a custom SDK to call the OIDC API. Here's an example:
public class OidcAuthCodeLoginHandler : MonoBehaviour
{
[SerializeField] Button OidcAuthCodeLoginButton;
[SerializeField] Text LoginStatus;
[SerializeField] private string clientId = "<YOUR_CLIENT_ID>";
[SerializeField] private string clientSecret = "<YOUR_CLIENT_SECRET>";
[SerializeField] private string redirectUri = "<YOUR_REDIRECT_URI>";
[SerializeField] private string authorizationUrl = "<YOUR_OIDC_AUTH_URL>";
[SerializeField] private string tokenUrl = "YOUR_OIDC_TOKEN_URL";
string[] scopes = { "<YOUR_OIDC_SCOPE>" };
// AccelByte's Multi Registry references
private ApiClient apiClient;
private User user;
private string serverURL = "<YOUR_SERVER_URL>"; // Replace with your server URL.
private HttpListener listener;
private void OnEnable()
{
LoginStatus.text = "Please Login";
OidcAuthCodeLoginButton.onClick.AddListener(() =>
{
LoginStatus.text = "Attempting Login";
StartLogin();
});
}
public void StartLogin()
{
string authorizationURL = $"{authorizationUrl}?response_type=code&client_id={clientId}&redirect_uri={redirectUri}&scope={string.Join(" ", scopes)}&state=foo";
// Start the local server
StartServer();
// Open the authorization URL in the default browser or WebView.
Application.OpenURL(authorizationURL);
// Wait for the response URL from the local server.
StartCoroutine(WaitForResponseURL());
}
private void StartServer()
{
listener = new HttpListener();
listener.Prefixes.Add(serverURL + "/");
listener.Start();
// Start a new thread to handle incoming HTTP requests.
ThreadPool.QueueUserWorkItem(HandleRequests);
}
private string authorizationCode;
private void HandleRequests(object state)
{
while (listener.IsListening)
{
try
{
HttpListenerContext context = listener.GetContext();
string responseURL = context.Request.Url.ToString();
// Check if the response URL contains the authorization code.
if (responseURL.Contains("code="))
{
// Extract the authorization code from the response URL.
authorizationCode = ExtractAuthorizationCode(responseURL);
if (authorizationCode != null)
{
// Continue with your authentication flow using the authorization code
// Send a response to the browser indicating success
byte[] responseBytes = System.Text.Encoding.UTF8.GetBytes("Authorization code received");
context.Response.OutputStream.Write(responseBytes, 0, responseBytes.Length);
context.Response.OutputStream.Close();
// Stop the local server
listener.Stop();
listener.Close();
// Call ExchangeAuthorizationCode on the main Unity thread
UnityMainThreadDispatcher.Instance().Enqueue(ExchangeAuthorizationCode());
break;
}
}
}
catch (Exception e)
{
Debug.LogError("Error handling request: " + e.Message);
}
}
}
private IEnumerator WaitForResponseURL()
{
while (true)
{
yield return new WaitForSeconds(1f); // Adjust the wait time as needed
// Send a request to the local server to check if the response URL has been received.
using (UnityWebRequest webRequest = UnityWebRequest.Get(serverURL))
{
yield return webRequest.SendWebRequest();
if (!webRequest.isNetworkError && !webRequest.isHttpError)
{
// Response received, break the loop.
Debug.Log("Response URL received: " + webRequest.downloadHandler.text);
break;
}
}
}
}
private string ExtractAuthorizationCode(string responseURL)
{
// Extract the code parameter from the response URL
Uri uri = new Uri(responseURL);
string query = uri.Query;
int codeIndex = query.IndexOf("code=");
int stateIndex = query.IndexOf("&state=");
if (codeIndex >= 0)
{
if (stateIndex > codeIndex)
{
// Extract the authorization code without the state parameter.
string authorizationCode = query.Substring(codeIndex + 5, stateIndex - (codeIndex + 5));
Debug.Log(authorizationCode);
return authorizationCode;
}
else
{
// State parameter not found; extract the authorization code until the end.
string authorizationCode = query.Substring(codeIndex + 5);
Debug.Log(authorizationCode);
return authorizationCode;
}
}
return null;
}
private IEnumerator ExchangeAuthorizationCode()
{
WWWForm form = new WWWForm();
form.AddField("code", authorizationCode);
form.AddField("client_id", clientId);
form.AddField("client_secret", clientSecret);
form.AddField("redirect_uri", redirectUri);
form.AddField("grant_type", "authorization_code");
using (UnityWebRequest www = UnityWebRequest.Post(tokenUrl, form))
{
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success)
{
string responseJson = www.downloadHandler.text;
string accessToken = ExtractAccessToken(responseJson);
LoginStatus.text = "Got Access Token" + accessToken;
apiClient = MultiRegistry.GetApiClient();
// Grab a reference to the current User, even though they have not been logged in yet.
// This also acts as the initialization point for the whole AccelByte plugin.
user = apiClient.GetApi<User, UserApi>();
Result<TokenData, OAuthError> loginResult = null;
user.LoginWithOtherPlatform(PlatformType.Oidcokta, accessToken, (Result<TokenData, OAuthError> loginResult) =>
{
if (loginResult.IsError)
{
// If it results in error, grab the Error Error and Description to print in the Log.
Debug.Log($"Login failed : {loginResult.Error.error} Description : {loginResult.Error.error_description}");
// Set the Status Text to display the Error, if any.
LoginStatus.text = $"Login failed : {loginResult.Error.error} Description : {loginResult.Error.error_description}";
}
else
{
Debug.Log($"Login successful : {loginResult.Value.ToJsonString()}");
LoginStatus.text = $"Success Login With oidc : {loginResult.Value.ToJsonString()} ";
}
// Enable interaction with the button again.
OidcAuthCodeLoginButton.interactable = true;
});
}
else
{
Debug.Log($"Token request error: {www.error}");
}
}
}
private string ExtractAccessToken(string responseJson)
{
// Parse the JSON response to extract the access token.
AccessTokenResponse accessTokenResponse = JsonUtility.FromJson<AccessTokenResponse>(responseJson);
// Extract the access token from the parsed response
string accessToken = accessTokenResponse.access_token;
return accessToken;
}
[System.Serializable]
public class AccessTokenResponse
{
public string access_token;
public int expires_in;
public string token_type;
}
}
The code below handles LoginWithOtherPlatform
, which is the part of the AccelByte SDK that handles third-party platform logins by auth token from the GetAuthSessionTicket
method.
// AccelByte's Multi Registry initialization
apiClient = MultiRegistry.GetApiClient();
//Grab a reference to the current User, even though they have not been logged in yet.
//This also acts as the initialisation point for the whole AccelByte plugin.
user = apiClient.GetApi<User, UserApi>();
Result<TokenData, OAuthError> loginResult = null;
user.LoginWithOtherPlatform(PlatformType.Oidcokta, accessToken, (Result<TokenData, OAuthError> loginResult) =>
{
if (loginResult.IsError)
{
// If it results in error, grab the Error Error and Description to print in the
Debug.Log($"Login failed : {loginResult.Error.error} Description : {loginResult.Error.error_description}");
// Set the Status Text to display the Error if there is any
LoginStatus.text = $"Login failed : {loginResult.Error.error} Description : {loginResult.Error.error_description}";
}
else
{
Debug.Log($"Login successful : {loginResult.Value.ToJsonString()}");
LoginStatus.text = $"Success Login With oidc : {loginResult.Value.ToJsonString()} ";
}
// Enable interaction with the button again.
OidcAuthCodeLoginButton.interactable = true;
});
The platform_token
for OIDC Authentication is Access Token from your IdP.
After adding the Login Handler Script to your project, you can compile it.
Sample code testing
You can build and run your project. The screenshot below demonstrates that the code works and we are able to login using OIDC and our test app.
Extend SDK Login
You can integrate your OIDC and sign in using OIDC credentials through Extend SDK.
- Go Extend SDK
- Python Extend SDK
- Java Extend SDK
- C# Extend SDK
func main() {
mux := http.NewServeMux()
// proceed
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./templates/index.html")
})
// config
mux.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
oidcConfig := struct {
Scope string `json:"scope"`
PlatformId string `json:"platform_id"`
DiscoveryUrl string `json:"discovery_url"`
ClientSecret string `json:"client_secret"`
ClientId string `json:"client_id"`
}{"openid profile email", platformId, discoveryUrl, os.Getenv("CLIENT_SECRET"), os.Getenv("CLIENT_ID")}
data, err := json.Marshal(oidcConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_, err = w.Write(data)
if err != nil {
return
}
}
})
// callback
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
form := url.Values{}
form.Add("grant_type", "authorization_code")
form.Add("client_id", os.Getenv("CLIENT_ID"))
form.Add("client_secret", os.Getenv("CLIENT_SECRET"))
form.Add("redirect_uri", "http://localhost")
form.Add("code", r.URL.Query().Get("code"))
post, errPost := http.PostForm("url", form)
if errPost != nil {
return
}
bodyBytes, err := io.ReadAll(post.Body)
if err != nil {
log.Fatal(err)
}
response := token{}
errUnmarshal := json.Unmarshal(bodyBytes, &response)
if errUnmarshal != nil {
return
}
// Login Platform from AGS
loginPlatform(*response.IDToken)
// Try to call AGS after login success
getCountryLocation()
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/text")
_, err = w.Write([]byte("Success Login"))
if err != nil {
return
}
})
log.Printf("listening on http://%s/", "127.0.0.1:8080")
log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux))
}
// Login platform using Go Extend SDK wrapper
func loginPlatform(platformToken string) {
input := &o_auth2_0.PlatformTokenGrantV3Params{
ClientID: &clientIDPhantAuth,
CreateHeadless: &createHeadless,
PlatformID: platformId,
PlatformToken: &platformToken,
}
err := oauth.LoginPlatform(input)
if err != nil {
log.Fatal("failed login platform")
return
} else {
log.Printf("successful login")
}
}
// Get country location using Go Extend SDK wrapper
func getCountryLocation() {
// Try to call AGS service
}
type token struct {
AccessToken *string `json:"access_token"`
IDToken *string `json:"id_token"`
RefreshToken *string `json:"refresh_token"`
TokenType *string `json:"token_type"`
}
import requests
import accelbyte_py_sdk.services.auth as auth_service
from accelbyte_py_sdk.core import AccelByteSDK
from accelbyte_py_sdk.core import MyConfigRepository
host = "https://example.com"
response = requests.get(
url="{host}/user/{username}/token/{tokenType}".format(host=host, username="YourUsername", tokenType="authorization")
)
code = response.text
response = requests.post(
url="{host}/auth/token".format(host=host),
data={
"grant_type": "authorization_code",
"client_id": "YourClientId",
"client_secret": "YourClientSecret",
"redirect_uri": "http://localhost/callback",
"code": code,
}
)
token = response.json()
sdk = AccelByteSDK()
sdk.initialize(
options={
"config": MyConfigRepository(
base_url="https://****.accelbyte.io",
client_id="********************************",
client_secret="********************************",
namespace="********",
)
}
)
result, error = auth_service.login_platform(
platform_id="YourAbPlatformId",
platform_token=token["id_token"],
sdk=sdk,
)
if error:
exit(error)
// OIDC provider credentials
@Builder
public class ProviderSpecification {
@JsonProperty("name")
public String name;
@JsonProperty("key")
public String key;
@JsonProperty("discovery_url")
public String discoveryUrl;
@JsonProperty("client_id")
public String clientId;
@JsonProperty("client_secret")
public String clientSecret;
@JsonProperty("scope")
public String scope;
}
// Discovery data from provider
@Builder
public class DiscoveryData {
@JsonProperty("issuer")
public String issuer;
@JsonProperty("jwks_uri")
public String jwksUri;
@JsonProperty("authorization_endpoint")
public String authEndpoint;
@JsonProperty("token_endpoint")
public String TokenEndpoint;
@JsonProperty("registration_endpoint")
public String registrationEndpoint;
@JsonProperty("introspection_endpoint")
public String introspectionEndpoint;
@JsonProperty("userinfo_endpoint")
public String userInfoEndpoint;
@JsonProperty("scopes_supported")
public List<String> supportedScopes;
@JsonProperty("grant_types_supported")
public List<String> supportedGrantTypes;
@JsonProperty("claims_supported")
public List<String> SupportedClaims = new ArrayList<>();
}
public static DiscoveryData retrieveDiscoveryData(HttpClient httpClient, String discoveryUrl) throws Exception {
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(discoveryUrl)).build();
HttpResponse<String> httpResponse;
try {
httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
// Do something when failed
throw new Exception("Encountered an exception when downloading");
}
if (httpResponse.statusCode() >= HttpURLConnection.HTTP_OK && httpResponse.statusCode() >= HttpURLConnection.HTTP_MULT_CHOICE) {
ObjectMapper objectMapper = new ObjectMapper();
DiscoveryData data = objectMapper.readValue(httpResponse.body(), DiscoveryData.class);
return data;
} else {
throw new Exception("Encountered unexpected http status: " + httpResponse.statusCode());
}
}
public static HttpRequest.BodyPublisher encodeContentToFormUrl(Map<Object, Object> data) throws UnsupportedEncodingException {
StringJoiner sj = new StringJoiner("&");
for (Map.Entry<Object, Object> entry : data.entrySet()) {
sj.add(URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8.toString()) + "=" +
URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8.toString()));
}
return HttpRequest.BodyPublishers.ofString(sj.toString());
}
// Method to get authorization token from provider
public Map<String, String> getAuthorizedToken(ProviderSpecification spec, String authorizationToken) throws Exception {
HttpClient httpClient = HttpClient.newHttpClient();
DiscoveryData dData = retrieveDiscoveryData(httpClient, spec.discoveryUrl);
Map<Object, Object> data = new HashMap<>();
data.put("grant_type", "authorization_code");
data.put("client_id", spec.clientId);
data.put("client_secret", spec.clientSecret);
data.put("redirect_uri", "http://localhost:9090/callback");
data.put("code", authorizationToken);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(dData.TokenEndpoint))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(encodeContentToFormUrl(data))
.build();
HttpResponse<String> httpResponse;
try {
httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
// Do something when failed
throw new Exception("Encountered error when fetching the authorized token");
}
if (httpResponse.statusCode() >= HttpURLConnection.HTTP_OK && httpResponse.statusCode() >= HttpURLConnection.HTTP_MULT_CHOICE)
{
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(httpResponse.body(), new TypeReference<HashMap<String,String>>() {});
} else {
throw new Exception("Encountered unexpected http return status: " + httpResponse.statusCode());
}
}
...
final AccelByteSDK sdk = new AccelByteSDK(httpClient, tokenRepository, configRepository);
var abPlatformId = "<ab platform id>";
var authCode = "<authorization code from provider>";
var spec = new ProviderSpecification(...); //specify or load provider credentials
Map<String, String> authToken = getAuthorizedToken(spec, authCode);
final String idToken = authToken.get("id_token");
if (!sdk.loginPlatform(abPlatformId, idToken))
{
throw new Exception("Login platform failed!");
}
// Do something when login succeeds
// OIDC provider credentials
public class ProviderSpecification
{
[JsonPropertyName("name")]
public string Name { get; set; } = String.Empty;
[JsonPropertyName("key")]
public string Key { get; set; } = String.Empty;
[JsonPropertyName("discovery_url")]
public string DiscoveryUrl { get; set; } = String.Empty;
[JsonPropertyName("client_id")]
public string ClientId { get; set; } = String.Empty;
[JsonPropertyName("client_secret")]
public string ClientSecret { get; set; } = String.Empty;
[JsonPropertyName("scope")]
public string Scope { get; set; } = String.Empty;
}
// Discovery data from provider
public class DiscoveryData
{
[JsonPropertyName("issuer")]
public string Issuer { get; set; } = String.Empty;
[JsonPropertyName("jwks_uri")]
public string JWKSUri { get; set; } = String.Empty;
[JsonPropertyName("authorization_endpoint")]
public string AuthEndpoint { get; set; } = String.Empty;
[JsonPropertyName("token_endpoint")]
public string TokenEndpoint { get; set; } = String.Empty;
[JsonPropertyName("registration_endpoint")]
public string RegistrationEndpoint { get; set; } = String.Empty;
[JsonPropertyName("introspection_endpoint")]
public string IntrospectionEndpoint { get; set; } = String.Empty;
[JsonPropertyName("userinfo_endpoint")]
public string UserInfoEndpoint { get; set; } = String.Empty;
[JsonPropertyName("scopes_supported")]
public List<string> SupportedScopes { get; set; } = new List<string>();
[JsonPropertyName("grant_types_supported")]
public List<string> SupportedGrantTypes { get; set; } = new List<string>();
[JsonPropertyName("claims_supported")]
public List<string> SupportedClaims { get; set; } = new List<string>();
public static DiscoveryData Retrieve(HttpClient httpClient, string discoveryUrl)
{
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, discoveryUrl);
HttpResponseMessage response = httpClient.Send(req);
string jsonString = response.Content.ReadAsStream().ReadToString();
return JsonSerializer.Deserialize<DiscoveryData>(jsonString)!;
}
}
// Method to get authorization token from provider
public OAuthTokens GetAuthorizedToken(ProviderSpecification spec, string authorizationToken)
{
DiscoveryData dData = DiscoveryData.Retrieve(DefaultHttpClient.Http, spec.DiscoveryUrl);
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, dData.TokenEndpoint);
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"grant_type", "authorization_code" },
{"client_id", spec.ClientId },
{"client_secret", spec.ClientSecret },
{"redirect_uri", "http://localhost:9090/callback" },
{"code", authorizationToken }
});
HttpResponseMessage response = DefaultHttpClient.Http.Send(req);
string jsonString = response.Content.ReadAsStream().ReadToString();
return JsonSerializer.Deserialize<OAuthTokens>(jsonString)!;
}
using IAccelByteSdk sdk = AccelByteSdk.Builder
.UseDefaultHttpClient()
.UseDefaultConfigRepository()
.UseDefaultTokenRepository()
.UseDefaultCredentialRepository()
.Build();
var abPlatformId = "<ab platform id>"
var authCode = "<authorization code from provider>";
var spec = new ProviderSpecification(); //specify or load provider credentials
OAuthTokens tokens = GetAuthorizedToken(spec, authCode);
try
{
string output = String.Empty;
sdk.LoginPlatform(abPlatformId, tokens.ID, (otr) =>
{
output = JsonSerializer.Serialize(otr);
});
// Do something when login succeeds
}
catch (Exception x)
{
// Do something when login fails
}