Obtain Authorization via Process Controller
Introduction
To simplify use cases that provide a one-time result - like the GiroIdent KYC check - finAPI introduced the Process Controller, which provides and manages an identity transparently for the customer with a simple two-step procedure. The Process Controller is a wrapper service on top of finAPI Access.
Process Controller allows to get an access_token
via a process token.
For this purpose, the create new process endpoint creates a user whose credentials are managed by it.
These users and all data are automatically deleted after some days, so that no user management is necessary.
Prerequisites
The major prerequisite is to have a valid set of client credentials: client_id
and client_secret
.
Security
A process token is basically a token for which a user is created in the background, which is only available for a limited time and is then completely deleted from the systems.
To ensure that the process token cannot be used by third parties to obtain an access_token
, it has 2 security mechanisms.
First, the exchange of the process token for an access_token is time-limited, which can be configured in days per mandator, and it can only be exchanged a maximum of 10 times.
In addition, the query parameter invalidate=true
can be specified when fetching the access_token
. This means that no further token exchange is possible.
A process token should therefore always be exchanged with an access_token
using the query parameter invalidate=true
.
This parameter should only be set to false
if a customer can resume the process at a later point in time.
TL:TR
Used Endpoints
In short, you need to call those 2 endpoints on the Process Controller to obtain a token.
Description | HTTP Method | Process Controller Endpoint | Link to API Doc |
---|---|---|---|
Create a process token | POST |
| |
Get | GET |
| https://docs.finapi.io/?product=processctl#get-/processes/-processToken- |
Process Overview
cURL Example
Translated into cURL it looks like the following:
Step 1 - Create a Process Token (cURL)
Create a Process Token that automatically contains a user that will be deleted after some time.
curl --location 'https://di-processctl-finapi-general-sandbox.finapi.io/api/v1/processes' \
--header 'Content-Type: application/json' \
--data '{
"clientId": "<your clientId>",
"clientSecret": "<your clientSecret>",
"clientReferences": [
{
"clientReference": "<customer-reference>",
"clientReferenceKey": "<customer key>"
}
],
"processId": "USER_ONLY",
"processTargetUrl": "https://finapi.io?token="
}'
The result looks like this:
{
"processes": [
{
"clientReference": "<customer-reference>",
"processToken": "8f92ec58-b9b9-11ec-8422-0242ac120002"
}
]
}
Step 2 - Exchange the Process Token into an access_token
(cURL)
Call the “Get access token for a process” endpoint with the process token from the previous request.
curl --location 'https://di-processctl-finapi-general-sandbox.finapi.io/api/v1/processes/<processToken>?invalidate=true'
The result looks like this:
{
"accessToken": "89e3b64c-b9ba-11ec-8422-0242ac120002"
}
Implementation Guide
See a full working project here: finAPI Data Intelligence Product Platform Examples (Bitbucket)
Code from this guide can be found here: Authentication with Process Controller (Bitbucket)
Environment overview can be found here: Environments
The guidelines and the example project are written in Kotlin with Spring Boot. But it is easily adoptable for other languages and frameworks.
For the HTTP connection, we are using here plain Java HttpClient
, to be not restrictive for the client selection and that everything is transparent.
Only the actual functionality is discussed in this guideline. Used helper classes or the models can be looked up in the source code.
However, we always recommend using a generated client from our API, which reduces the effort of creating models and functions to access the endpoints and eliminates potential sources of error.
To learn how to generate and apply the API client using the Process Controller "Create user and exchange with access_token" as an example, please see the Getting Started - Code Generator (SDK) Integration.
Please also note that the code presented here and also the repository is only an illustration of a possible implementation. Therefore, the code is kept very simple and should not be used 1:1 in a production environment.
Step 1 - Create a Process Token
In the first step, we create a process token, which allows us to exchange it with an access_token
without having the user management.
This can be done against the Process Controller
To create a process token, the endpoint POST /api/v1/processes
must be called.
As a body, we need the clientId
and the clientSecret
on the one hand, as well as clientReferences
and a processId
.
For more information have a look at our Process Controller Create Process Token API Documentation
Field | Type | Mandatory |
---|---|---|
| The finAPI | yes |
| The finAPI | yes |
| The Multiple elements are only required, if you want to prepare for example an e-mail campaign. In this case you could create multiple links with one call. See also Process Controller Create Batch Processes API Documentation if you are interested in this multi-process creation. | yes |
| The | yes |
|
| no |
| This ID was created to have some sepratation between the processes. For other use-cases please have a look at the API Documentation. | yes |
First, we’ll create a component ProcessCtlAuthorization
with a function to create the process.
In this example, we inject the URL of the Process Controller, the clientId,
and clientSecret
, so that this is not hard coded.
The first method will be the creation of the request object. The models, which are used here, can be found in the Bitbucket repository or can be generated out of the API.
@Component
class ProcessCtlAuthorization(
@Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
@Value("\${finapi.security.credentials.clientId}") private val clientId: String,
@Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
private val objectMapper: ObjectMapper
) {
/**
* This method creates the request object for the Create Process Token endpoint.
* We are using a data object to create the structure,
* because it is much easier to maintain than a pure string representation.
*
* In the end, we map it to a JSON string.
*/
private fun createProcessTokenModel(
clientReference: String,
clientReferenceKey: String? = null
): String {
// create clientReferences object
val clientReferences = CreateProcessTokenClientReferences(
clientReference = clientReference,
clientReferenceKey = clientReferenceKey
)
// create request body
val createProcessTokenModel = CreateProcessTokenRequestModel(
clientId = clientId,
clientSecret = clientSecret,
clientReferences = listOf(clientReferences),
processId = ProcessId.USER_ONLY
)
// map the object to a JSON String representation
return objectMapper.writeValueAsString(createProcessTokenModel)
}
companion object {
private val log = KotlinLogging.logger {}
private const val URI_CREATE_PROCESS_TOKEN = "/processes"
private const val URI_GET_ACCESS_TOKEN = "/processes/"
}
}
Now we have the body, let’s continue to send the request to the Process Controller.
To do that, we do need a HttpClient
instance and an HttpRequest
object.
The request should contain now the URL of the Process Controller and the path of the create token endpoint. In our case, we have already injected the URL of the Process Controller. This URL contains also the /api/v1
basepath.
After this request was sent, we have to check, that the result was 2xx
and if this was the case, we want to map it back to an object, to access the data inside the JSON.
This mapped result will be returned then to the caller.
If something went wrong, we’ll throw an exception.
Please note that throwing a simple RuntimeException
for a production system is not a good idea. Please use your own exception, which will then be handled correctly in your code.
@Component
class ProcessCtlAuthorization(
@Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
@Value("\${finapi.security.credentials.clientId}") private val clientId: String,
@Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
private val objectMapper: ObjectMapper
) {
[...]
/**
* Calls the Create Process Token endpoint of the Process Controller.
*
* After this was done, check, if the state was 2xx or else throw an exception.
*/
@Throws(RuntimeException::class)
private fun createProcessToken(clientRef: String, clientRefKey: String? = null): CreateProcessTokenResponse {
// prepare the client
val client = HttpClient.newBuilder().build()
val request = HttpRequest.newBuilder()
// build URL for https://<processctl>/api/v1/processes
.uri(URI.create("${processCtlUrl}${URI_CREATE_PROCESS_TOKEN}"))
// set Content-Type header to application/json
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
// add the body
.POST(
HttpRequest.BodyPublishers.ofString(
createProcessTokenModel(
clientReference = clientRef,
clientReferenceKey = clientRefKey
)
)
).build()
// send the request and fetch the response
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
// check for status code is 2xx or log and throw an exception
StatusCodeCheckUtils.checkStatusCodeAndLogErrorMessage(
response = response,
errorMessage = "Unable to create a user."
)
// return the object of the mapped result
return objectMapper.readValue(response.body(), CreateProcessTokenResponse::class.java)
}
}
After this step was done, we should have a process token.
Step 2 - Exchange the Process Token into an access_token
The process token must now be converted into a real access_token
, to be able to call restricted endpoints.
To achieve this, we will now create a function, which is calling the GET /processes/{processToken}
endpoint of the Process Controller.
On this endpoint, we can invalidate the process token, if we don’t want to get another access_token
for this user.
The query parameter ?invalidate=true
invalidates the process token after getting the access_token
. In this case no new access_token
can be obtained.
This ensures, that nobody else can access the data, which was maybe imported with this token and deletes all user data after a day.
This function should return only the access_token
as a String if everything is ok.
Else, we do also throw an Exception here.
@Component
class ProcessCtlAuthorization(
@Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
@Value("\${finapi.security.credentials.clientId}") private val clientId: String,
@Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
private val objectMapper: ObjectMapper
) {
[...]
/**
* Calls the Get Access Token from Process Token endpoint of the Process Controller.
*
* After this was done, check, if the state was 2xx or else throw an exception.
*/
@Throws(RuntimeException::class)
private fun exchangeProcessTokenToAccessToken(processToken: String): String {
// prepare the client
val client = HttpClient.newBuilder().build()
val request = HttpRequest.newBuilder()
// build URL for https://<processctl>/api/v1/processes/{processId}
// we do use also ?invalidate=true to make the process token no longer available to others
.uri(URI.create("${processCtlUrl}${URI_GET_ACCESS_TOKEN}${processToken}?invalidate=true"))
// set Content-Type header to application/json
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.GET()
.build()
// send the request and fetch the response
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
// check for status code is 2xx or log and throw an exception
StatusCodeCheckUtils.checkStatusCodeAndLogErrorMessage(
response = response,
errorMessage = "Unable to exchange process token."
)
// map the result to object and return access token
val resultObj = objectMapper.readValue(response.body(), GetAccessTokenByProcessTokenModel::class.java)
return resultObj.accessToken
}
}
After this is now also done, we could write the public function, which should bring both endpoints together.
In this function, we call our createProcessToken()
function with a client reference as UUID, because we only want to have the user without any relation in our system.
If you wish to have this relation, you can give the clientReference
also to the obtainAccessToken()
function.
The result, in this case, could only be an object with one process. So we finally call our exchangeProcessTokenToAccessToken()
function, to get the result.
This result is then returned and can be used by the caller function as a Bearer
token in the Authorization
header.
@Component
class ProcessCtlAuthorization(
@Value("\${finapi.instances.processctl.url}") private val processCtlUrl: String,
@Value("\${finapi.security.credentials.clientId}") private val clientId: String,
@Value("\${finapi.security.credentials.clientSecret}") private val clientSecret: String,
private val objectMapper: ObjectMapper
) {
[...]
/**
* Obtain an access_token by creating first a process token and exchange it.
*/
@Throws(RuntimeException::class)
fun obtainAccessToken(): String {
val createProcessResult = createProcessToken(
clientRef = UUID.randomUUID().toString(),
)
return exchangeProcessTokenToAccessToken(createProcessResult.processes[0].processToken)
}
}