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:

  1. Add a new enum value.
  2. Create an Identity Provider Plugin class.
  3. Configure the Identity Provider Plugin in Pragma Engine.
  4. [optional] Set up Portal login.

Add a new enum value #

  1. In the AccountRpcExt.proto file, update the ExtIdProvider to have a custom enum value (DEMO):
enum ExtIdProvider {
    EXT_UNUSED = 0;
    reserved 1 to 100; // Pragma supported Identity Providers

    //  === EXTENSIONS (101+) ===
    DEMO = 101; // Add this
}
  1. Run the following command to build the protos: make ext-protos.

Create an Identity Provider Plugin class #

To create a custom Identity Plugin implement this interface:

interface IdentityProviderPlugin: IdentityProvider, PragmaServicePlugin

Below is an example class implementing this interface:

// DemoIdentityProviderPlugin.kt

class DemoIdentityProviderPlugin(
    override val service: Service,
    override val contentDataNodeService: ContentDataNodeService
) : ConfigurablePlugin<DemoIdProviderConfig>, IdentityProviderPlugin {
    override fun getType() = ExtIdProvider.DEMO_VALUE

    override var canAuthenticate: Boolean = true
    override var separator: String = ""

    private lateinit var config: DemoIdProviderConfig
    private var httpClient:
    TimedHttpClient = TimedHttpClient("demo.outgoing.http", service)

    constructor(
        service: Service,
        contentDataNodeService: ContentDataNodeService,
        timedHttpClient: TimedHttpClient
    ) : this(service, contentDataNodeService) {
        this.httpClient = timedHttpClient
    }

    override suspend fun onConfigChanged(config: DemoIdProviderConfig) {
        this.config = config
    }

    // Code to always validate providerToken
    override suspend fun validate(providerToken: String): IdProviderAccount {
        return IdProviderAccount(
            ExtIdProvider.DEMO_VALUE,
            id_provider_user_id,
            id_provider_display_name,
            id_provider_discriminator
        )
    }

    // 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(),
            // any additional required fields
        )
    }
}
Method overview

The getType function needs to return the new enum value(AccountRpcExt.ExtIdProvider.DEMO_VALUE) for your custom identity provider.

The validate function takes in the providerToken and makes the appropriate third party calls to your custom identity provider. If valid, this function returns IdProviderAccount. If invalid, this function should throw a Pragma exception.

if (!validFromThirdParty) {
    throw PragmaException(
      PragmaError.AccountService_Unverified,
      "Account not verified.")
}

The getPublicInfo function should return all information necessary for a third party to use your custom identity provider. The Pragma Engine Portal uses this information to allow login.

getPublicInfo should not return any secret or sensitive information.

Create a config class #

Below is an example of a plugin config:

// DemoIdentityProviderConfig.kt

import pragma.config.ConfigBackendModeFactory
import pragma.settings.BackendType

class DemoIdProviderConfig private constructor(type: BackendType) :
    IdentityProviderPluginConfig<DemoIdProviderConfig>(type) {
        override val description = "Demo Id 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<DemoIdProviderConfig> {
            override fun getFor(type: BackendType) : DemoIdProviderConfig {
                return DemoIdProviderConfig(type)
            }
        }
}

Any configurable settings for this plugin can be added in the config class.

You can find a list of the supported types in the inner class Types which is found in the file platform/2-pragma/core/src/main/kotlin/pragma/config/ConfigObject.kt.

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: "pragma.account.DemoIdentityProviderPlugin"
          config:
            clientId: "your-client-id"
            clientSecret: "encrypted-string-for-your-client-secret"

[optional] Set up Portal login #

To enable Portal login you’ll need to do the following:

  1. Go to the 5-ext/web/portal/src/authProviders directory.
  2. Create a demo folder.
  3. In the demo directory, create a file named demo.js.
import SignInDemo from './SignInDemo.vue.js'
import { demoAuthStep1_InitAndRedirect } from './demoAuth.js'

export default {
  id: 'DEMO',
  name: 'Demo',
  iconName: 'demo-css-icon',
  onSignInButtonClick: ({ router }) => {
    demoAuthStep1_InitAndRedirect()
  },
  // these components will be rendered inside the usual Signin box
  registeredRoutes: [
    {
      name: 'SignInDemo',
      path: '/signin-demo',
      component: SignInDemo
    }
  ],
  // should return true if the url is forcely changed
  routerBeforeHook: ({ router }) => {
    return false
  }
}
  1. 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, getIdProviderConfig } = pragma.auth

export const REDIRECT_PATH = '/signin-demo'
export const authenticateDemo = async token =>
  await signIn({
    providerId: 'DEMO',
    providerToken: token
  })

export const demoAuthStep1_InitAndRedirect = () => {
  const config = getIdProviderConfig('DEMO')

  ...
  Code to request auth page of provider
  ...

  utils.goToUrl(demoAuthorizeUrl)
}

export const demoAuthStep2_GetTokenAndAuthenticate = async code => {
  const token = await getDemoAuthToken(code)
  if (!token) return { ok: false, error: 'Invalid code.' }

  return await authenticateDemo(token)
}

const getDemoAuthToken = async code => {
  ...
  Code to request auth token using oauth code
  ...
}

export default {
  REDIRECT_PATH,
  authenticateDemo,
  demoAuthStep1_InitAndRedirect,
  demoAuthStep2_GetTokenAndAuthenticate,
  getDemoAuthToken
}