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:
- An AFAS App Connector exists.
- The App Connector is configured for OAuth client credentials as OAuth-only (no hybrid setup with classic token + OAuth).
- The following values are available:
- 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
- Make a backup copy of
persons.ps1, departments.ps1, and configuration.json.
- Apply each change below: replace the Before block with the After block, or insert the block at the indicated anchor.
- Do not remove unrelated customer customizations.
- 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:
After:
$clientId = $c.ClientId
$clientSecret = $c.ClientSecret
departments.ps1 — replace the token variable assignment:
Before:
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:
- The App Connector in AFAS is not configured for OAuth client credentials.
ClientId or ClientSecret was copied incorrectly.
- The App Connector does not have the required GetConnector permissions.
- The customer still uses old script content in only one of the two files.
Short Checklist
- Arrange OAuth client credentials in AFAS.
- Update
configuration.json: add ClientId and ClientSecret, remove Token.
- Replace
$c.Token usage in both scripts with ClientId and ClientSecret.
- Remove
AfasToken logic and use Get-AFASConnectorData -Headers $headers.
- Insert the inline OAuth token retrieval block in both scripts.
- Search for leftover classic-auth keywords.
- Run a Preview in HelloID for
persons.ps1 and departments.ps1.
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.ps1anddepartments.ps1.Scope
Apply these changes to:
persons.ps1departments.ps1configuration.jsonPreconditions In AFAS
Before changing the HelloID connector, make sure the customer has arranged the following in AFAS Profit:
ClientIdClientSecretImportant: 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_v2T4E_HelloID_Employments_v2T4E_HelloID_Positions_v2T4E_HelloID_OrganizationalUnits_v2T4E_HelloID_Groups_v2T4E_HelloID_UserGroups_v2Summary Of The Change
The classic flow:
$c.TokenAuthorization: AfasToken ...The OAuth flow:
$c.ClientIdand$c.ClientSecrethttps://.../ProfitRestServices/oauth/tokenAuthorization: Bearer ...Recommended Working Method
persons.ps1,departments.ps1, andconfiguration.json.Step 1: Update configuration.json
Remove (located after the
BaseUrlobject):{ "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
BaseUrlobject:{ "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:
After:
departments.ps1 — replace the token variable assignment:
Before:
After:
persons.ps1 — replace the start log line:
Before:
After:
departments.ps1 — replace the start log line:
Before:
After:
Step 3: Change Get-AFASConnectorData to use headers instead of Token
Both scripts — two changes inside the
Get-AFASConnectorDatafunction:Replace the parameter:
Before:
After:
Remove this block entirely:
After these changes,
Get-AFASConnectorDatauses only the incoming$Headersvariable.Step 4: Insert the OAuth token retrieval before the first AFAS data call
Both scripts — insert this block after
#endregion functionsand before the first query block:Step 5: Replace all Get-AFASConnectorData calls
Both scripts — replace every call that passes
-Token:Before:
After:
Step 6: Search terms to confirm classic auth is fully removed
After editing, search both scripts for these terms:
$c.Token$token =AfasTokenToBase64StringASCII.GetBytes-TokenExpected result: no active matches remain.
Validation
After the migration, run a Preview in HelloID for both
persons.ps1anddepartments.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:
ClientIdorClientSecretwas copied incorrectly.Short Checklist
configuration.json: addClientIdandClientSecret, removeToken.$c.Tokenusage in both scripts withClientIdandClientSecret.AfasTokenlogic and useGet-AFASConnectorData -Headers $headers.persons.ps1anddepartments.ps1.