Instead of having the anonymous user communicate with the Workflows, have the anonymous user submit the document to Data Services, and let your backend start the workflow as a system user.
This is both architecturally cleaner and avoids the authentication problem entirely:
- The anonymous user submits their document to Data Services (see here).
- A backend component (e.g., a Spring
@EventListeneronDocumentAfterCreateEventor similar) picks up the new document and starts the workflow as a system user — using eitherAPI_KEYorCERTIFICATEauthentication, which don’t require a logged-in user.
This way, the DS-to-WF communication always happens with a proper system identity, and you never have to handle the “no bearer token” case in your workflow trigger code.
How: Use the Workflows Java Client
A12 Workflows provides a dedicated artifact workflows-extension-client that gives you a proper Java API instead of raw RestTemplate calls. It provides constants for RPC method names via WorkflowsOperationConstants and works with Data Services’ RpcOperationsClient and RequestBuilderFactory:
requestBuilderFactory
.newJsonRpc2RequestBuilder()
.addMethodCall(WorkflowsOperationConstants.START_PROCESS_OPERATION)
.id(...)
.putParameter(...)
.putParameter(...)
.build()
This is authentication-agnostic — the underlying UAA REST Client handles the authentication method (DELEGATED, API_KEY, CERTIFICATE, etc.) based on configuration, not code. You configure the authentication type via properties like:
mgmtp.a12.uaa.authentication.client.rest.authentication-type=API_KEY
mgmtp.a12.uaa.authentication.client.rest.api-key-resource=classpath:/keys/system-api-key.crt
Architecture Flow
The standard A12 Workflows architecture works like this:
- Client sends a
START_PROCESSJSON-RPC request to Data Services - The Workflows Extension (registered in DS) handles this request
- The extension forwards the start request to CIB 7 (the process engine)
- CIB 7 starts a new process instance
By triggering step 1 from your backend (as a system user) rather than from the anonymous user’s session, the authentication is always well-defined.
Implementation
Here is a working reference implementation based on the A12 Fullstack Project Template.
1. Event Listener — Trigger the Workflow on Document Creation
The core piece is a @Component that listens for DocumentAfterCreateEvent. When an anonymous user creates an OrderProcess_DM document, it starts the OrderProcessWithoutInput BPMN process — but only after the transaction commits, to ensure the document is fully persisted before the workflow reads it.
@Component
public class StartWorkflowListener {
private RequestBuilderFactory requestBuilderFactory;
private RpcOperationsClient rpcOperationsClient;
public StartWorkflowListener(
RequestBuilderFactory requestBuilderFactory,
ClientFactoryProvider clientFactoryProvider
) {
this.requestBuilderFactory = requestBuilderFactory;
this.rpcOperationsClient = clientFactoryProvider.getSelfClientFactory().getRpcOperationsClient();
}
@CommonDataServicesEventListener
public void listenForCreation(DocumentAfterCreateEvent event) {
if (
"OrderProcess_DM".equals(event.getDataServicesDocument().getMetadata().getDocumentModelReference())
&& "anonymous".equals(event.getDataServicesDocument().getMetadata().getCreator())
) {
TransactionSynchronizationManager.registerSynchronization(
new AfterCommitSynchronization(() -> startWorkflow(
event.getDataServicesDocument().getMetadata().getDocRef()))
);
}
}
private void startWorkflow(DocumentReference docRef) {
var variables = new ObjectMapper().createObjectNode()
.put("documentReference", docRef.toString())
.put("newDocRef", docRef.toString());
var request = requestBuilderFactory
.newJsonRpc2RequestBuilder()
.addMethodCall(WorkflowsOperationConstants.START_PROCESS_OPERATION)
.id("startProcess")
.putParameter("processDefinition", "OrderProcessWithoutInput")
.putParameter("businessKey", UUID.randomUUID().toString())
.putParameter("variables", variables)
.build();
rpcOperationsClient.invoke(List.of(request));
}
}
Key points:
@CommonDataServicesEventListenerhooks into the Data Services event system (not standard Spring events).- The
creatormetadata field is"anonymous"for unauthenticated users — use this to filter which documents should trigger workflows. AfterCommitSynchronizationdefers the RPC call until after the creating transaction commits, preventing race conditions where the workflow tries to load a document that doesn’t exist yet.
2. Client Factory — Authenticate as a System User
The RpcOperationsClient used by the listener must authenticate as a system user (not the anonymous session). ClientFactoryProvider builds a ClientFactory using API_KEY authentication, reusing the UAA autoconfiguration properties:
@Component
public class ClientFactoryProvider {
@Value("${mgmtp.a12.dataservices.client.configuration.base-url}")
public String baseUrl;
private UAARestClientAutoconfigProperties uaaRestClientAutoconfigProperties;
public ClientFactoryProvider(UAARestClientAutoconfigProperties uaaRestClientAutoconfigProperties) {
this.uaaRestClientAutoconfigProperties = uaaRestClientAutoconfigProperties;
}
public ClientFactory getSelfClientFactory() {
UAARestClientProperties restClientProperties = new UAARestClientProperties();
restClientProperties.setAuthorizationHeaderName("Authorization");
restClientProperties.setUaaBase(uaaRestClientAutoconfigProperties.getRest().getUaaBase());
restClientProperties.setAuthenticationType(AuthenticationType.API_KEY);
restClientProperties.setApiKeyResource(
uaaRestClientAutoconfigProperties.getRest().getApiKeyResource());
var clientConfiguration = new ClientConfiguration();
clientConfiguration.setBaseUrl(baseUrl);
return ClientFactory.builder(restClientProperties, clientConfiguration).build();
}
}
This factory creates a client that authenticates via API_KEY — the same mechanism used by other server-to-server calls in A12. The baseUrl points back to the Data Services server itself, since the workflow start is an RPC operation routed through DS.
3. Transaction Safety — AfterCommitSynchronization
A small utility that implements Spring’s TransactionSynchronization to defer an action until after the current transaction commits:
public final class AfterCommitSynchronization implements TransactionSynchronization {
private final Runnable runnable;
public AfterCommitSynchronization(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void afterCommit() {
runnable.run();
}
}
Without this, the START_PROCESS call could fire before the document is committed to the database, causing the workflow to fail when it tries to load the document.
4. Client-Side — Conditional Button Visibility
On the frontend, an ExtendedFormEngine component controls which buttons anonymous vs. authenticated users see. Anonymous users only see Submit; authenticated users see Save, Proceed, and Cancel order:
const ExtendedFormEngine: ComponentType<View> = function ExtendedFormEngine(props) {
const authenticationState = useSelector(UaaSelectors.state);
const enablements = useMemo(
() => ({
byButtonName: {
"Cancel order": { hidden: authenticationState !== AuthenticationState.AUTHENTICATED },
Save: { hidden: authenticationState !== AuthenticationState.AUTHENTICATED },
Proceed: { hidden: authenticationState !== AuthenticationState.AUTHENTICATED },
Submit: { hidden: authenticationState !== AuthenticationState.NOT_AUTHENTICATED }
}
}),
[authenticationState]
);
return (
<ActivityContext value={props.activityId}>
<FormEngineViews.FormEngine {...props} enablements={enablements} />
</ActivityContext>
);
};
Register it in your viewProvider.tsx by replacing the default FormEngine view:
FormEngine(props) {
return <ExtendedFormEngine {...props} />;
},
5. Model Changes
Several A12 models were adapted to support anonymous workflow triggering.
Application Model — Orders Module for Anonymous Users
A new OrdersModule_anonymous module was added to YourAppModel_anonymous_AM.json. It provides a “New Order” menu entry that directly opens a blank OrderProcess_DM document form (via instance: "__NEW__"). The module defines its own flow and scene so that anonymous users land on the OrderProcess_FM form inside a MasterDetail layout.
Document & Form Models — Anonymous Role Access
The roles annotation was extended from "admin,user" to "admin,user,anonymous" on three models so unauthenticated users can interact with them:
OrderProcess_DM.json— the order document modelOrderProcess_FM.json— the order form modelWorkflowsMetadata_DM.json— the workflows metadata document model
Form Model — Submit Button for Anonymous Users
A new Submit button (event: event_submit) was added to OrderProcess_FM.json alongside the existing Proceed button. Both buttons exist in the model; the ExtendedFormEngine on the client side (see section 4) controls visibility — anonymous users only see Submit, authenticated users only see Proceed/Save/Cancel.
Important: Syncing Document Fields into Process Variables
When a document model uses the availableInProcessAs annotation on fields, those field values are normally synchronized into process variables automatically during the standard workflow start flow. However, when the process is started programmatically with an already existing document (as in this approach), this automatic sync does not happen. You must explicitly add a “Sync Available Fields Delegate” service task (syncAvailableFieldsDelegate) to your BPMN process — typically right after the start event and before the first user task that needs access to those variables.
In addition, you should enable the workflows-automatic-sync Spring profile on the Workflows Engine. This profile ensures that document fields annotated with availableInProcessAs are automatically kept in sync with process variables whenever they change during the lifetime of the process. The explicit service task handles the initial sync at process start, while the Spring profile takes care of ongoing updates.
6. Configuration
Add the required dependency in server/app/build.gradle:
implementation a12Libs.dataservices.client
End-to-End Flow
- Anonymous user opens the app and sees a restricted UI (anonymous application model)
- Anonymous user fills in the order form and clicks Submit
- Data Services creates the document with
creator: "anonymous" StartWorkflowListenercatches theDocumentAfterCreateEvent, waits for the transaction to commit, then starts theOrderProcessWithoutInputBPMN process as a system user viaAPI_KEYauthentication- Authenticated users (e.g., back-office staff) see the document in their workflow task list with Save, Proceed, and Cancel order buttons
Relevant Documentation
- Workflows - How It Works — architecture overview of DS-to-WF communication
- Workflows - Java Client — the
workflows-extension-clientartifact andWorkflowsOperationConstants - UAA - Authentication Types — API_KEY, DELEGATED, CERTIFICATE configuration
- Workflows Extension - Events — Spring events you can use to hook into the workflow lifecycle