Skip to content

OAuth Migration Runbook from Classic Token #26

Description

@mverbraak-t4e

AFAS OAuth Migration Runbook

Purpose

This document is intended to migrate an existing customer AFAS Profit source connector from classic token authentication to OAuth client credentials.

This runbook is intentionally based on targeted search/replace and code insertion. Do not overwrite the full scripts, because customer-specific changes may already exist in persons.ps1 and departments.ps1.

Scope

Apply these changes to:

  • persons.ps1
  • departments.ps1
  • configuration.json

Preconditions In AFAS

Before changing the HelloID connector, make sure the customer has arranged the following in AFAS Profit:

  1. An AFAS App Connector exists.
  2. The App Connector is configured for OAuth client credentials as OAuth-only (no hybrid setup with classic token + OAuth).
  3. The following values are available:
    • ClientId
    • ClientSecret
  4. The App Connector has access to the same GetConnectors as the old classic-token setup.

Important: advise customers to use an OAuth-only App Connector. AFAS states that in September 2026 existing Classic tokens will automatically get an end date of 15 February 2027. From that date those tokens no longer work. New Classic tokens can still be created with another end date until 31 August 2027. App connectors that have not been used for 6 months or longer are blocked.

See AFAS documentation for more information: Eigen app connector inrichten in vogelvlucht and Eigen app connector beheren.

Required GetConnectors:

  • T4E_HelloID_Users_v2
  • T4E_HelloID_Employments_v2
  • T4E_HelloID_Positions_v2
  • T4E_HelloID_OrganizationalUnits_v2
  • T4E_HelloID_Groups_v2
  • T4E_HelloID_UserGroups_v2

Summary Of The Change

The classic flow:

  • reads $c.Token
  • Base64-encodes the token
  • sends Authorization: AfasToken ...

The OAuth flow:

  • reads $c.ClientId and $c.ClientSecret
  • requests an access token from https://.../ProfitRestServices/oauth/token
  • sends Authorization: Bearer ...

Recommended Working Method

  1. Make a backup copy of persons.ps1, departments.ps1, and configuration.json.
  2. Apply each change below: replace the Before block with the After block, or insert the block at the indicated anchor.
  3. Do not remove unrelated customer customizations.
  4. Validate both imports after the update.

Step 1: Update configuration.json

Remove (located after the BaseUrl object):

  {
    "key": "Token",
    "type": "input",
    "templateOptions": {
      "label": "Token",
      "type": "password",
      "placeholder": "Example: <token><version>1</version><data>555012345678901234567890123456789</data></token>",
      "description": "The AppConnector token to connect to Profit.",
      "required": true
    }
  },

Insert after the BaseUrl object:

  {
    "key": "ClientId",
    "type": "input",
    "templateOptions": {
      "label": "ClientId",
      "placeholder": "Example: 2f8a2f4d-9a5f-41d8-aec8-6ab123456789",
      "description": "The OAuth client id of the AFAS AppConnector.",
      "required": true
    }
  },
  {
    "key": "ClientSecret",
    "type": "input",
    "templateOptions": {
      "label": "ClientSecret",
      "type": "password",
      "placeholder": "Example: 1234567890abcdef1234567890abcdef12345678",
      "description": "The OAuth client secret of the AFAS AppConnector.",
      "required": true
    }
  },

Step 2: Update the configuration variables in persons.ps1 and departments.ps1

persons.ps1 — replace the token variable assignment:

Before:

$token = $c.Token

After:

$clientId = $c.ClientId
$clientSecret = $c.ClientSecret

departments.ps1 — replace the token variable assignment:

Before:

$token = $($c.Token)

After:

$clientId = $($c.ClientId)
$clientSecret = $($c.ClientSecret)

persons.ps1 — replace the start log line:

Before:

Write-Information "Start person import: Base URL: $baseUri, Using positionsAction: $positionsAction, token length: $($token.length)"

After:

Write-Information "Start person import: Base URL: $baseUri, Using positionsAction: $positionsAction, Using OAuth client credentials"

departments.ps1 — replace the start log line:

Before:

Write-Information "Start department import: Base URL: $baseUri, token length: $($token.length)"

After:

Write-Information "Start department import: Base URL: $baseUri, Using OAuth client credentials"

Step 3: Change Get-AFASConnectorData to use headers instead of Token

Both scripts — two changes inside the Get-AFASConnectorData function:

Replace the parameter:

Before:

[parameter(Mandatory = $true)]$Token,

After:

[parameter(Mandatory = $true)]$Headers,

Remove this block entirely:

$encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Token))
$authValue = "AfasToken $encodedToken"
$Headers = @{ Authorization = $authValue }
$Headers.Add("IntegrationId", "45963_140664") # Fixed value - Tools4ever Partner Integration ID

After these changes, Get-AFASConnectorData uses only the incoming $Headers variable.

Step 4: Insert the OAuth token retrieval before the first AFAS data call

Both scripts — insert this block after #endregion functions and before the first query block:

# Obtain OAuth access token
try {
    $tokenUri = "$baseUri/oauth/token"
    Write-Verbose "Requesting OAuth access token from [$tokenUri]"

    $tokenRequestBody = @{
        grant_type    = 'client_credentials'
        client_id     = $clientId
        client_secret = $clientSecret
    }

    $tokenResponse = Invoke-RestMethod -Method Post -Uri $tokenUri -Body $tokenRequestBody -ContentType 'application/x-www-form-urlencoded' -UseBasicParsing

    if ([String]::IsNullOrWhiteSpace([String]$tokenResponse.access_token)) {
        throw "OAuth token endpoint did not return an access_token."
    }

    if ([String]::IsNullOrWhiteSpace([String]$tokenResponse.token_type) -or ([String]$tokenResponse.token_type).ToLowerInvariant() -ne 'bearer') {
        throw "OAuth token endpoint returned an unexpected token_type [$($tokenResponse.token_type)]. Expected [Bearer]."
    }

    $headers = @{ Authorization = "$($tokenResponse.token_type) $($tokenResponse.access_token)" }
    $headers.Add("IntegrationId", "45963_140664") # Fixed value - Tools4ever Partner Integration ID

    Write-Information "Successfully obtained OAuth access token"
}
catch {
    $ex = $PSItem
    $errorMessage = Get-ErrorMessage -ErrorObject $ex
    Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
    throw "Could not obtain OAuth access token. Error Message: $($errorMessage.AuditErrorMessage)"
}

Step 5: Replace all Get-AFASConnectorData calls

Both scripts — replace every call that passes -Token:

Before:

Get-AFASConnectorData -Token $token ...

After:

Get-AFASConnectorData -Headers $headers ...

Step 6: Search terms to confirm classic auth is fully removed

After editing, search both scripts for these terms:

  • $c.Token
  • $token =
  • AfasToken
  • ToBase64String
  • ASCII.GetBytes
  • -Token

Expected result: no active matches remain.

Validation

After the migration, run a Preview in HelloID for both persons.ps1 and departments.ps1.

The Preview output indicates whether the migration was applied correctly and whether the source import runs successfully with OAuth.

Common Failure Causes

If the migration is technically correct but the connector still fails, check these first:

  1. The App Connector in AFAS is not configured for OAuth client credentials.
  2. ClientId or ClientSecret was copied incorrectly.
  3. The App Connector does not have the required GetConnector permissions.
  4. The customer still uses old script content in only one of the two files.

Short Checklist

  1. Arrange OAuth client credentials in AFAS.
  2. Update configuration.json: add ClientId and ClientSecret, remove Token.
  3. Replace $c.Token usage in both scripts with ClientId and ClientSecret.
  4. Remove AfasToken logic and use Get-AFASConnectorData -Headers $headers.
  5. Insert the inline OAuth token retrieval block in both scripts.
  6. Search for leftover classic-auth keywords.
  7. Run a Preview in HelloID for persons.ps1 and departments.ps1.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions