https://dev.oneground.nl/blog OneGround ZGW API Blog 2025-11-14T00:00:00.000Z https://github.com/jpmonette/feed Latest insights and best practices for ZGW API implementation and Dutch case management https://dev.oneground.nl/img/favicon.png <![CDATA[Why OneGround Uses an OAuth2 Token Endpoint]]> https://dev.oneground.nl/blog/oauth2-token-endpoint 2025-11-14T00:00:00.000Z We have previously published an article on JWT best practices, which outlines the standards for creating secure and reliable JSON Web Tokens. It covers essential practices like using proper claims (iss, exp), managing secrets securely, and keeping tokens short-lived.

This raises an important question: Who is responsible for implementing these security rules?

In some API designs, customers are asked to generate their own JWTs. The approach is seemingly straightforward: "Here is a secret key. Please create a JWT according to our guidelines, sign it, and include it in your Authorization header."

However, this model shifts the complex and critical responsibility of security onto you, the developer of a consumer application. At OneGround, we believe that security should be a shared responsibility, but the burden of token creation should lie with the API provider. This is why we have started using a standard OAuth2 Token Endpoint, which allows you to request a token from us instead of creating one yourself.

This article explains why our approach is more secure, reliable, and ultimately simpler for you.

The Problem with "Bring Your Own JWT"

When an API provider asks you to create your own JWT, you become the token issuer (iss). This means you are responsible for managing the entire token generation process securely. While this might seem to offer flexibility, it introduces significant risks and complexities, not because of any oversight on your part, but because token generation is a sensitive security function.

Here are some of the challenges with this model:

  • Lack of Enforced Token Expiration: A critical security practice is to use short-lived tokens. If you were to generate your own tokens, you would be responsible for managing their expiration. While our guidelines might suggest a one-hour expiration, it would be technically possible to create tokens with very long lifetimes, for example, to work around re-authentication logic. Such long-lived tokens would expose your application and our API to security risks, such as replay attacks, if a token is ever compromised. To prevent this, we would need to add complex validation checks on our side, which is a reactive security measure, not a proactive one.

  • It Creates Secret Management Burdens: To enable you to sign JWTs, we would have to share a signing secret with you. This would place the burden of protecting that secret entirely on you. If the secret were accidentally leaked—for instance, by being committed to a code repository, embedded in a client-side application, or logged in plain text—an attacker could create valid tokens indefinitely. This would pose a significant security threat to your integration and data.

  • You Have No Control Over the Implementation: To generate JWTs, your developers would need to select and use a library for the language of their choice. This introduces the risk of using outdated or insecure libraries that may contain vulnerabilities. For example, some older JWT libraries were susceptible to the alg: "none" vulnerability, where they would accept a token without a signature. This would allow an attacker to forge tokens and bypass security checks entirely.

  • It Increases API Complexity: In a "bring your own JWT" model, our API would need to perform extensive validation on every single request to check for inconsistencies in how different customers implement their token generation. We would have to defensively validate every claim (iss, aud, exp) to ensure they are correctly implemented, adding overhead and complexity.

The Secure and Simple Alternative: The OAuth2 Token Endpoint

The OAuth2 framework, specifically the Client Credentials Grant flow, provides a standardized and much more secure solution that we use at OneGround.

Here’s how it works:

  1. Request: Your application sends a secure HTTPS POST request to our token endpoint (e.g., /oauth/token).
  2. Credentials: In this request, you include the client_id and client_secret that we provide to you.
  3. Validation: Our server validates these credentials against our secure credential store.
  4. Issuance: If the credentials are valid, our server generates a new JWT with all the correct claims (iss, aud, exp, etc.).
  5. Signing: Our server signs the JWT using a secure, asymmetric algorithm (like RS256) with our private key. This key never leaves our server, which is a major security advantage over sharing a secret with you.
  6. Response: The signed JWT is returned to your application as an access token.
  7. Usage: Your application includes this short-lived JWT in the Authorization header of your API requests. When it expires, you simply request a new one.

OAuth2 Token Endpoint Practical Example

Here’s what a token request and its usage look like in practice:

Request:

POST /token HTTP/1.1
Host: idp.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=abc123xyz
&client_secret=very-secure-secret-here

Response:

{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600
}

Using the token:

GET /api/resources HTTP/1.1
Host: zgw-api.example.com
Authorization: Bearer eyJhbGciOi...

With this model, you don't have to worry about the internal structure of the JWT, signing algorithms, or claim management. You simply exchange your credentials for a ready-to-use token.

Why the Token Endpoint Model is Better for You

This model ensures that the responsibility for creating secure tokens remains with us, the API provider. As the issuer (iss), we have full control over the security of the token generation process.

  • We Maintain All Best Practices on Your Behalf

    • Token Lifetime: We set and enforce how long tokens are valid. If we decide they should expire in 15 minutes for security reasons, that is enforced for everyone. You don't have to worry about it.
    • Claims: Because we create the token, we guarantee that all necessary claims (iss, aud, exp) are accurate, present, and standardized.
    • Algorithm: We choose and manage the signing algorithm, such as the industry-standard RS256. Your application only needs to use the token, not understand its cryptographic implementation.
  • Private Signing Key Stays Secret

    • The client_secret we provide you is not a signing key, it functions more like a password for your application to authenticate itself when requesting a token. The risk of a leak is significantly lower:
      • An attacker with a leaked client_secret can only request tokens, not create them. We can detect and rate-limit this activity.
      • We can quickly revoke a compromised client_id or rotate your credentials without affecting the entire system's security. Revoking a client_secret is a straightforward, immediate action.
      • This is far more secure than a leaked signing key, which would allow an attacker to forge valid tokens undetected. In contrast, rotating a compromised signing key is a complex process that could impact multiple systems and requires careful coordination.
  • It's Easier for Your Developers

    • Your developers don't need to research JWT libraries, manage signing keys, or worry about getting security claims right. Their only task is to make a single, standard HTTP POST request. This is a common task that any developer can implement in any language without specialized security knowledge.
  • You Benefit from Better Security Controls

    • Rate Limiting: We protect our token endpoint with rate limiting to prevent brute-force attacks on client credentials.
    • Token Revocation: If your credentials are ever compromised, we can revoke your client_id immediately, cutting off all future token requests from that ID.
    • Scope-Based Access: We can issue tokens with specific permissions (scopes) based on your client's needs, ensuring your application has only the access it requires (the principle of least privilege).
    • Audit Trails: We maintain logs of all token requests, allowing us to detect suspicious patterns and investigate potential unauthorized access attempts.
    • Short-Lived Tokens: We enforce short token lifetimes (e.g., 15-60 minutes) to minimize the risk if a token is ever intercepted.

OneGround's Commitment to Secure Authentication

At OneGround, we are committed to providing the most secure and reliable API experience. That's why we have implemented an OAuth2 Token Endpoint across our platform and we are giving possibility for all our customers to generate tokens through this standardized and secure approach.

By centralizing token creation, we ensure consistent security practices for all our customers, eliminate common vulnerabilities associated with client-side token generation, and simplify the integration process for your development teams. You no longer need to worry about JWT creation, signing algorithms, or claim validation. Instead, you can confidently and easily request a token from our endpoint to access our APIs.

Of course, we understand that this may require some adjustments on each customer application, so we are still supporting legacy integration during this transition period. Additionally, we are expecting that in the future (like ZGW 2.x) self-signed tokens will be deprecated in favor of centralized OAuth2 flows. Our approach not only enhances security today but also ensures your integration remains compliant and robust for years to come.

You can read more about our implementation and how to use the OAuth2 Token Endpoint in our ClientID Management and JWT Authentication in OneGround documentation.

Conclusion

JSON Web Tokens are an excellent standard for securely transmitting information in APIs. However, the creation of these tokens is a sensitive security function that should be managed by the API provider, not the customer.

By using a standard OAuth2 Token Endpoint, we provide a more secure and reliable authentication system. We handle the complexities of token generation, allowing you to focus on building your application. This approach simplifies development, reduces security risks, and ensures that best practices are followed consistently.

When a token expires, your application simply requests a new one. For the Client Credentials flow, this is a straightforward process that does not require complex refresh token logic, keeping your integration simple while maintaining a high level of security.

References

]]>
Giedrius Grabauskas https://github.com/Grabauskas
<![CDATA[Standardized Document Signing Integration for ZGW APIs - Complete Guide]]> https://dev.oneground.nl/blog/integrating-signing-software-with-zgw 2025-03-27T00:00:00.000Z Introduction

Digitally signing documents is a common task in the workflow of handling a case. Several software vendors offer solutions for this. This article describes a standardized pattern to handle the signing of documents within the ZGW API landscape. Most API-calls in this pattern are already part of the ZGW standard. The missing link was a way to initiate the signing transaction. In this article we propose a standardized trigger message for this purpose.

Signing sequence

The basic flow for signing documents is as follows:

  • A user working on a case in a task specific application (TSA) or case management system (ZAC) selects one or more documents related to the case to be signed by one or more signers
  • The TSA or ZAC sends a trigger message to the signing software
  • The signing software retrieves the documents from the DRC and presents them to the signers
  • When all signers have signed the documents, the signing software uploads the signed versions of the documents to the DRC. Depending on the vendor and configuration of the signing software, it might also add a separate evidence document with proof of signing to the case.
  • The signing software sends a notification to the NRC to indicate signing is complete
  • The TSA or ZAC handles this notification, for instance by informing the user

Below is a sequence diagram with a more detailed view of this flow. Sequence diagram Use this link to view the diagram in full size.

ZGW Compliance

Most API requests in this sequence are standard requests already defined in the ZGW specification, with the following two remarks:

  • The ZGW standard does not provide in a way to initiate a signing transaction. This article proposes a standardized format for this trigger message. This is the only non-ZGW request in this sequence.
  • The notification to the NRC at the end of the signing flow is a standard request following the NRC specification. The channel used is a custom channel "documentacties". This channel needs to be registered in the NRC for the signing flow to work. This is a one-tima action that should be done by the administrator of the ZGW implementation. Registering a new channel is a standard request within the NRC specification, so all compliant ZGW implementations should support adding this channel. This same channel might be used for other events concerning documents in the future.

Scope of this article

This article describes the interaction between the APIs of the TSA or ZAC, the signing software and the ZGW components (DRC, ZRC and NRC). The user interaction for selecting the documents and signers is out of scope, this is the responsibility of the TSA or ZAC. The same goes for the user interaction of the actual signing of the documents, that is the responsibility of the signing software.

Existing implementations

The pattern described here is already implemented and used in production settings by the signing applications ValidSign and Zynyo and by the TSA Rx.Mission. These existing implementations use OneGround as the ZGW implementation, but as this pattern only uses standard behaviour of the ZGW components, it should work on any compliant ZGW implementation.

Technical details

Authentication

Authentication on the ZGW requests

The requests to the ZGW components use the standard authentication for the ZGW API: a JWT generated based on a ClientId and secret. See the OneGround documentation or the general ZGW documentation for details.

Authentication on the trigger message

The type of authentication on the trigger message is determined by the signing software and might vary between vendors. Acceptable authentication methods include usage of an api-key or a bearer token based on a ClientId and Secret. Note that in the case of ClientId and Secret, this will be a separate ClientId from the one used to access the ZGW API. The TSA or ZAC sending the trigger message should implement the authentication method for the signing software vendor(s) it integrates with.

Authentication of the signer

Before the documents are presented to the signer, this person should authenticate themselves. Handling this authentication is the responsibility of the signing software, however the required type of authentication can be set in the trigger message send by the TSA or ZAC. See the details of the trigger message for more on this.

Trigger message

To initiate the signing process, the TSA or ZAC should post a trigger message to the signing software in this format:

{
"naam": "...", // Name of signing transaction, can be displayed to the signer(s) by the signing software
"eigenaar": "[email protected]", // e-mail of user that initiates the signing transaction
"zaak": {
"uuid": "2cc803a9-7404-4289-8f3d-f2300f666eac", // uuid of the case
"url": "$url" // ZRC url of the case
},
"bewijs_informatieobjecttype": "$url", // informationobjecttype for evidence document
"documenten": [ // list of documents to sign
{
"uuid": "f491249e-0651-4d22-a8a8-bb35885e9245", // uuid of the document
"url": "$url", // DRC url of the document
"titel": "Some document", // title of the document
"versie": 1, // version of the document to be signed
"bestandsnaam" : "some file.docx" // filename of the document;
}
],
"ondertekenaars": [ // list of signers
{
"e-mail": "[email protected]", // e-mail of signer
"voornaam": "John", // first name of signer
"achternaam": "Doe", // last name of signer
"identificatie": "Signer1", // string to identify the signer; can be used to relate this signer to specific signing areas or placeholders in the documents (not all signing software might need this property but supplying it should not cause errors)
"volgorde": 1, // order number used to determine in which order the signers should sign in case of multiple signers
"authenticatie": { // Method of authentication this signer should use to identify themselves
"methode": "sms",
"waarde": "+31655555555" // phone number of signer
}
}
]
}

Some details about properties that might not be clear from the description:

  • bewijs_informatieobjecttype: Depending on the vendor and/or configuration of the signing software, a separate evidence document with proof of signing might be added to the case as part of the signing flow. This property specifies which informatieobjecttype should be used for this document.
  • documenten[ ].versie: The exact version of the document to be signed. The signing software should retrieve this version to present it to the signers.
  • documenten[ ].bestandsnaam: Signing software often accepts different document formats (i.e. Microsoft Word, Open Office, PDF) as input, but the signed version will always be PDF. In case the format of the signed version differs from the original, the filename should be updated with the new extension.
  • ondertekenaars[ ].identificatie: A string that can be used by the signing software to determine where in the document the signature of this signer should appear (usage of this field might vary between vendors and not all signing software might require this field)
  • ondertekenaars[ ].authenticatie: This property indicates how the signer should authenticate themselves before the documents to be signed are presented to them. The following options should be supported at minimum:
    • authenticatie = null: when this property is set to null, the minimal form of authentication should be used. What this minimal is, is dependent on the signing software.
    • authenticatie.methode = sms: The signer should authenticate themselves using an sms with a one-time passcode (SMS OTP); for this method the property authenticatie.waarde should also be provided, with the mobile phone number of the signer in international format.

Response to the trigger message

If the trigger message is received correctly, the signing software should respond with status code 200 and this message:

{
"transaction_id": "2opbDxqSB8BX3137ulqdw2q9_Mw=" // unique id of this signing transaction; format to be determined by the signing software
}

NRC notification when signing is complete or has failed

When the signing is complete and the signing software has uploaded the signed documents to the DRC, it should send a notification to the NRC. The TSA or ZAC can subscribe to these notifications, for instance to notify the user that initiated the signing request.

Note that signing documents is a very asynchronous sequence: it might be several hours, days, or even longer before all signers have signed all documents in the signing request. That means the time between sending the trigger message and receiving the NRC notification can be quite long. It is recommended that while the signing transaction is in progress, the TSA or ZAC indicates this to users in the UI where the documents are displayed so the user knows these documents are awaiting signing.

When signing is completed successfully, the request to the NRC should be:

{
"kanaal": "documentacties",
"hoofdObject": ""$url"", // ZRC url of the case
"resource": "zaak",
"resourceUrl": ""$url"", // ZRC url of the case
"actie": "OndertekenenVoltooid", // this value indicates this notification is about a successfully completed signing transaction
"aanmaakdatum": "2025-01-01T12:00:00Z", // date-time of this notification
"kenmerken": {
"transaction_id": "2opbDxqSB8BX3137ulqdw2q9_Mw=" // id of this signing transaction, same as in the response on the trigger message
}
}

When signing has failed, is cancelled or has otherwise not completed successfully, the request to the NRC should be:

{
"kanaal": "documentacties",
"hoofdObject": ""$url"", // ZRC url of the case
"resource": "zaak",
"resourceUrl": ""$url"", // ZRC url of the case
"actie": "OndertekenenAfgebroken", // this value indicates this notification is about a failed signing transaction
"aanmaakdatum": "2025-01-01T12:00:00Z", // date-time of this notification
"kenmerken": {
"reden": "reason", // string specifying why the transaction has not completed, for instance "refused by signer", "retracted" or "technical error ..." (preferably in Dutch, so it can be presented to the end user in the TSA or ZAC)
"transaction_id": "2opbDxqSB8BX3137ulqdw2q9_Mw=" // Id of this signing transaction, same as in the response on the trigger message
}
}

Some details about these notifications:

  • kanaal = documentacties: As there is no channel for this action defined in the ZGW specification and it is not allowed to post custom notifications to existing channels, this article proposes a custom channel for actions on documents. Adding new channels is allowed within the NRC specification. This also ensures only applications interested in these notifications will receive them.
  • resource = zaak: It might seem counter-intuitive that the resource of this notification is zaak instead of document and that the resourceUrl and hoofdObject properties are set to the url of the case. The reason for this is that one signing transaction can concern multiple documents but will always be initiated from the context of a single case.
]]>
Michiel Nijdam
<![CDATA[Best Practices for JWT Usage in APIs - OneGround Security Guide]]> https://dev.oneground.nl/blog/best-practices-for-jwt-usage-in-apis 2024-12-03T00:00:00.000Z JSON Web Tokens (JWT) is an essential part of modern API ecosystems, providing a secure and efficient method for verifying user identities and exchanging information between parties. However, as with any security mechanism, improper implementation can introduce vulnerabilities.

Core Concepts of JWT

A JWT is a compact, URL-safe token format that encodes information in three parts:

  1. Header: Specifies the token type and signing algorithm.
  2. Payload: Carries the claims, which include user-related data or other metadata.
  3. Signature: Validates the token's integrity using a secret key or public/private key pair.

When used appropriately, JWTs enable stateless authentication, eliminating the need for the server to store session data. The token itself holds all essential authentication information and expires after a defined period.

JWT Usage Recommendations for Your Applications

To maximize security, we recommend the following best practices:

1. Use Properly Configured Claims

JWT claims are key-value pairs encoded into the token's payload. While you can include custom claims based on your API's needs, several standard claims form the backbone of a well-constructed JWT:

  • iss (Issuer): Identifies the entity that issued the JWT. Always explicitly set the iss claim to indicate the origin of the token. Both the API and consuming applications should agree on the expected iss value in advance, and the application generating the JWT should include its name or link in this claim. Tokens with missing or incorrect iss values may be rejected by the API for security purposes.

  • exp (Expiration Time): Sets the token's expiration date and time using a Unix timestamp. This ensures the token cannot be used indefinitely, even if it is compromised.

  • aud (Audience): Defines the intended audience for the token. By specifying the aud claim, an API can ensure that the token is meant for its use and not an unrelated service.

  • iat (Issued At): The timestamp of when the token was created, which can help detect tokens created in the future by malicious actors.

2. Limit Token Lifetimes (exp)

Long-lived tokens pose a significant risk if they are leaked or intercepted. To protect sensitive resources, set an appropriate expiration time (exp) when creating tokens (from 5 minutes to a few hours) and avoid using long expiration times (e.g., months or years) for tokens, especially those used in production.

So, using short-lived tokens reduces the window for potential misuse when the token is compromised. Even if an unauthorized party gains access to a token, the inherent time restriction ensures its usability is limited.

3. Secure Your Secrets

When generating JWTs signed with symmetric algorithms, a secret key is used to sign and validate the token. If this secret is exposed, attackers can create their valid tokens. To prevent this:

  • Store secrets securely and never expose them in source code or client-side applications.
  • Rotate signing secrets periodically, at least each year, to minimize the impact of a potential breach.

4. Use Trusted Libraries

When implementing JWT functionality in your applications, selecting the correct library is crucial for ensuring security and reliability.

  • Use well-established libraries that are actively maintained, widely used, and trusted for their security.
  • Keep libraries up to date to address vulnerabilities and use dependency management tools to track outdated versions.
  • Avoid building custom implementations for JWT tokens, as they can lead to security gaps and missed edge-case scenarios.

5. Assign Each Client ID to a Single Application (client_id)

Each JWT token should be associated with a unique client ID for improved security and accountability, which must be dedicated to only one application.

  • Traceability: Enables effective tracking and auditing of each client’s actions.
  • Prevent Token Sharing: Ensures tokens are not shared across multiple clients, minimizing the risk of unauthorized access.
  • Revocation: Allows tokens issued to specific clients to be revoked without disrupting others.

Final Thoughts

JWT tokens are a powerful tool for securing APIs, but their effectiveness depends on proper configuration and usage. By following the best practices - such as setting appropriate claims, and limiting token lifetime — you can build a robust and secure authentication system for your APIs.

For detailed guidance on implementing JWT in your specific environment, you can find recommendations in JWT libraries and tools for your programming language or framework. Always stay informed about the latest security updates to evolve your implementation as threats and technologies change.

References

]]>
Giedrius Grabauskas https://github.com/Grabauskas