Custom Identity Providers #
Pragma Engine offers the flexibility to create multiple custom identity providers. To create a custom identity provider, there are several steps that need to be taken:
- Add a new enum value.
- Create a config class.
- Create an Identity Provider Plugin class.
- Configure the Identity Provider Plugin in Pragma Engine.
- [optional] Set up Portal login.
Add a new enum value #
- Update the
ExtIdProvider
to have a custom enum value (DEMO
):
- Run the following command to build the protos:
make ext-protos
.
Create a config class #
Create the DemoIdentityProviderConfig.kt
file under platform/5-ext/ext/src/main/kotlin/ext/account
.
Below is an example of a plugin config:
// DemoIdentityProviderConfig.kt
package ext.account
import pragma.account.config.IdentityProviderPluginConfig
import pragma.config.ConfigBackendModeFactory
import pragma.settings.BackendType
class DemoIdentityProviderConfig private constructor(type: BackendType) :
IdentityProviderPluginConfig<DemoIdentityProviderConfig>(type) {
override val description = "Demo Identity Provider configuration."
var clientSecret by types.encryptedString("your app's secret key")
var clientId by types.string("your app's public id")
companion object : ConfigBackendModeFactory<DemoIdentityProviderConfig> {
override fun getFor(type: BackendType) : DemoIdentityProviderConfig {
return DemoIdentityProviderConfig(type)
}
}
}
Any configurable settings for this plugin can be added in the config class.
You can find a list of the supported types for configurable settings in theinner class Types
which is found in the fileplatform/2-pragma/core/src/main/kotlin/pragma/config/ConfigObject.kt
.
Create an Identity Provider Plugin class #
To create a custom Identity Provider Plugin implement this interface:
interface IdentityProviderPlugin: IdentityProvider, PragmaServicePlugin
Below is an example class implementing this interface:
// DemoIdentityProviderPlugin.kt
package ext.account
import pragma.account.ExtIdProvider
import pragma.account.IdentityProviderPlugin
import pragma.auth.IdProviderAccount
import pragma.content.ContentDataNodeService
import pragma.http.TimedHttpClient
import pragma.plugins.ConfigurablePlugin
import pragma.services.Service
class DemoIdentityProviderPlugin(
override val service: Service,
override val contentDataNodeService: ContentDataNodeService
) : ConfigurablePlugin<DemoIdentityProviderConfig>, IdentityProviderPlugin {
override fun getType() = ExtIdProvider.DEMO_VALUE
override var canAuthenticate: Boolean = true
override var separator: String = ""
override lateinit var config: DemoIdentityProviderConfig
private var httpClient:
TimedHttpClient = TimedHttpClient("demo.outgoing.http", service)
private var accountNumber = 0
constructor(
service: Service,
contentDataNodeService: ContentDataNodeService,
timedHttpClient: TimedHttpClient
) : this(service, contentDataNodeService) {
this.httpClient = timedHttpClient
}
override suspend fun onConfigChanged(config: DemoIdentityProviderConfig) {
this.config = config
}
// Code to always validate providerToken
override suspend fun validate(providerToken: String): IdProviderAccount {
accountNumber++
return IdProviderAccount(
ExtIdProvider.DEMO_VALUE,
"demo-user-account-id-$accountNumber",
"demo-user-display-name-$accountNumber",
"demo-user-discrimator-$accountNumber",
)
}
// Code to return information Portal needs to use as Identity Provider
override fun getPublicInfo(): Map<String, String> {
return mapOf(
"clientId" to config.clientId,
"showPortalLoginButton" to config.showPortalLoginButton.toString(),
"playerLoginEnabled" to config.playerLoginEnabled.toString(),
"operatorLoginEnabled" to config.operatorLoginEnabled.toString(),
"accountLinkingEnabled" to config.accountLinkingEnabled.toString()
// any additional required fields
)
}
}
getPublicInfo
should not return any secret or sensitive information.
Configure the identity provider #
To enable and configure your custom Identity Provider Plugin, you’ll need to add the plugin config to your chosen Pragma Engine yaml
. For local development this would be in platform/5-ext/config/local-dev.yml
.
Below is an example of enabling your custom identity provider:
social:
pluginConfigs:
AccountService.identityProviderPlugins:
plugins:
Demo:
class: "ext.account.DemoIdentityProviderPlugin"
config:
clientId: "your-client-id"
clientSecret: "encrypted-string-for-your-client-secret"
Test with Postman #
To test that your custom identity is properly hooked up to Pragma Engine, you’ll need to first enable playerLoginEnabled
:
social:
pluginConfigs:
AccountService.identityProviderPlugins:
plugins:
Demo:
class: "ext.account.DemoIdentityProviderPlugin"
config:
clientId: "your-client-id"
clientSecret: "encrypted-string-for-your-client-secret"
playerLoginEnabled: true
Run Pragma Engine via one of the following methods.
Once the engine has started successfully, it prints the message [main] INFO main - Pragma server startup complete
.
Send a request to v1/account/authenticateorcreatev2
with the following data:
{
"providerId": 101, // your custom identity provider ID
"providerToken": "demo-token",
"gameShardId":"00000000-0000-0000-0000-000000000001",
"loginQueuePassToken": "{{loginQueueToken}}"
}
See Login queue for more details.
[optional] Set up Portal login #
To enable Portal login you’ll need to do the following:
- Go to the
platform/web/portal/src/authProviders
directory. - Create a
demo
folder. - In the
demo
directory, create a file nameddemo.js
.
import SignInDemo from './SignInDemo.vue.js'
import LinkDemo from './LinkDemo.vue.js'
import { Demo, demoAuthStep1_InitAndRedirect } from './demoAuth.js'
const utils = pragma.utils
const { getRedirectUri } = pragma.auth
export default {
id: 'DEMO',
name: 'Demo',
iconName: 'demo',
onSignInButtonClick: ({ router }) => {
demoAuthStep1_InitAndRedirect(getRedirectUri(Demo.SIGNIN_REDIRECT_PATH))
},
onLinkButtonClick: ({ router }) => {
demoAuthStep1_InitAndRedirect(getRedirectUri(Demo.LINK_REDIRECT_PATH))
},
// these components will be rendered inside the usual Signin box
registeredRoutes: [
{
name: 'SignInDemo',
path: '/signin-demo',
component: SignInDemo
},
],
registeredLinkRoutes: [
{
name: 'LinkDemo',
path: '/link-demo',
component: LinkDemo
},
],
// should return true if the url is forcibly changed
routerBeforeHook: ({ router }) => {
let url = utils.getWindowLocation().href
if (url.search('#/access_token=') >= 0) {
url = url.replace('#/access_token=', '&access_token=')
utils.goToUrl(url)
return true
}
}
}
- Create a file named
demoAuth.js
. This is where you define logic to communicate with your custom identity provider.
const { store } = pragma
const { utils } = pragma
const { signIn, link, getIdProviderConfig, getAuthenticateBaseUri, getRedirectUri } = pragma.auth
export const Demo = {
SIGNIN_REDIRECT_PATH: '/redirect/SignInDemo',
LINK_REDIRECT_PATH: '/redirect/LinkDemo',
PROVIDER_ID: 'DEMO',
BACKEND_REDIRECT: '/v1/account/oauth-redirect/demo',
}
export const authenticateDemo = async token =>
await signIn({
providerId: Demo.PROVIDER_ID,
providerToken: token
})
export const linkDemo = async token =>
await link({
providerId: Demo.PROVIDER_ID,
providerToken: token
})
export const demoAuthStep1_InitAndRedirect = (redirectUri) => {
const config = getIdProviderConfig(Demo.PROVIDER_ID)
// Code to request auth page of provider
utils.goToUrl(`${redirectUri}?code=demo-code`) // Replace this with auth page of provider
}
export const demoAuthStep2_GetTokenAndAuthenticate = async code => {
const token = await getDemoAuthToken(code, getRedirectUri(Demo.SIGNIN_REDIRECT_PATH))
if (!token) return { ok: false, error: 'Invalid code.' }
return await authenticateDemo(token)
}
export const demoAuthStep2_GetTokenAndLink = async code => {
const token = await getDemoAuthToken(code, getRedirectUri(Demo.SIGNIN_REDIRECT_PATH))
if (!token) return { ok: false, error: 'Invalid code.' }
return await linkDemo(token)
}
const getDemoAuthToken = async (code, redirectUri) => {
// Code to request auth token using auth code
return 'demo-token'
}
export default {
Demo,
demoAuthStep1_InitAndRedirect,
demoAuthStep2_GetTokenAndAuthenticate,
}
- Create a file named
LinkDemo.vue.js
. This is the intermediate redirect page used for the link account flow.
import { demoAuthStep2_GetTokenAndLink } from './demoAuth.js'
const eventEmitter = pragma.ui.helpers.eventEmitter
const initiateLinkAndRedirectWithCode = pragma.auth.initiateLinkAndRedirectWithCode
const LinkDemo = {
name: 'LinkDemo',
components: {},
emits: ['linkResult'],
setup(_props, { emit }) {
initiateLinkAndRedirectWithCode(
demoAuthStep2_GetTokenAndLink,
'Demo',
emit,
eventEmitter
)
return {}
},
template: `<div/>`
}
export default LinkDemo
- Create a file named
SignInDemo.vue.js
. This is the intermediate redirect page used for the sign in flow.
import { Demo, demoAuthStep2_GetTokenAndAuthenticate } from './demoAuth.js'
const { Icon, IdProviderAuthResultHandler } = pragma.ui.components
const { initiateAuthenticateAndRedirectWithCode } = pragma.auth
const SignInDemo = {
name: 'SignInDemo',
components: { Icon, IdProviderAuthResultHandler },
emits: ['success'],
setup(_props, { emit }) {
const authResultRef = Vue.ref()
initiateAuthenticateAndRedirectWithCode(
demoAuthStep2_GetTokenAndAuthenticate,
authResultRef,
)
return {
Demo,
authResultRef,
emitSuccess: () => emit('success')
}
},
template: `
<div>
<IdProviderAuthResultHandler
v-if='authResultRef'
:idProviderType='Demo.PROVIDER_ID'
:authResult='authResultRef'
@success='emitSuccess'
/>
<div v-else class='loading'></div>
</div>
`
}
export default SignInDemo
Test with Portal #
To test your custom identity provider with Portal, add the following configurations:
social:
pluginConfigs:
AccountService.identityProviderPlugins:
plugins:
Demo:
class: "demo.account.DemoIdentityProviderPlugin"
config:
clientId: "your-client-id"
clientSecret: "encrypted-string-for-your-client-secret"
showPortalLoginButton: true
accountLinkingEnabled: true
operatorLoginEnabled: true
playerLoginEnabled: true
Run the following command: make portal-package
Open the Social Player Portal (this is generally localhost:11000) and log into your newly created custom identity provider.