Mobile apps¶
Marie opens the leave app on her phone. The app sends her to the workflow site, in the phone's browser, and she signs in there with her own password. Back in the app, she sees the two requests waiting for her approval, and approves one. The workflow site never told the app her password, and the app holds nothing that could act for anybody else.
This page shows how to build such an app on Orchestra, with no server of its own in between.
How it works¶
The app signs in with standard OAuth: the authorization code grant with PKCE, through a public consumer, in the system browser (RFC 8252). The app keeps the access token and the refresh token in the device's keychain.
Every call the app makes with the access token is made for Marie:
- The token is the identity proof, of the
tokenscheme. The app sends noactor_prooforinitiator_proof. - Orchestra checks Marie's own permissions, as its pages do. A request she may not see is invisible to her, and a change she may not make is refused.
- The token's scope caps what it can do. A token with the
orchestrascope carries only the permissions the Orchestra API needs, however much more the person may do on the site. - The tenant is the consumer's, as for any caller.
A calling site, which has a server and its own client credentials, keeps working as it does today. See Distributed execution.
Setting up the workflow site¶
The consumer¶
Add a consumer for the app, under Configuration > Web services > Consumers:
| Field | Value |
|---|---|
| Client ID | leave-app |
| Is Confidential? | Unchecked. An app cannot keep a secret. |
| Use PKCE? | Checked. |
| Redirect URIs | The app's universal link (iOS) or app link (Android), such as https://leave.example.com/oauth/callback. |
| Grant types | Authorization Code and Refresh Token. |
| Scopes | orchestra |
| Orchestra tenant | The tenant the app works in. |
| Identity proofs accepted | The person, signed in through OAuth. |
A public consumer cannot get a token of its own: the client credentials grant needs a secret. So every call the app makes is made for a person.
The API refuses a token a person signed in for with a 403 when its consumer is not granted The person, signed in through OAuth. So a token that another app of the site got for its own purposes cannot reach the Orchestra API.
The scope¶
Orchestra Server ships the orchestra scope. It groups one scope for each
permission the API needs:
| Scope | Permission |
|---|---|
orchestra_access_api |
Access the Orchestra HTTP API (read) |
orchestra_drive_api |
Drive the Orchestra HTTP API (write) |
orchestra_access_instances |
Access Orchestra process instances |
orchestra_administer |
Administer Orchestra |
orchestra_process_tasks |
Process Orchestra tasks |
orchestra_view_own_instances |
View own Orchestra process instances |
A token's permissions are the person's own, kept to those its scope names. So the scope never gives Marie a permission she does not have. It only keeps the app's token away from everything else she may do on the site.
On a site that already had Orchestra Server installed, the scopes are not there yet. Install what Orchestra Server ships, and nothing else:
drush php:eval '$directory = \Drupal::service("extension.list.module")->getPath("orchestra_server") . "/config/optional";
\Drupal::service("config.installer")->installOptionalConfig(new \Drupal\Core\Config\FileStorage($directory));'
The people¶
Marie needs these permissions, in one of her roles:
- Access the Orchestra HTTP API (read) and Drive the Orchestra HTTP API (write). Only the API's routes use them, and only with an OAuth token, so they open nothing else.
- Process Orchestra tasks, to see and complete her tasks.
- View own Orchestra process instances, to see the requests she started.
- Grant OAuth2 codes, from Simple OAuth, to approve the app on the site's authorize page. It grants nothing else. Out of the box only administrators have it, and no scope can give it: the site checks it at the authorize step, on Marie's own session, before any token exists.
Access Orchestra process instances lets a person read every instance of the tenant, through the app as on the instance pages. Grant it only to the people who may, such as the operators of a service, not to everyone using the app.
Signing in¶
Use a standard library such as AppAuth. It opens the browser on the authorization endpoint:
https://workflows.example.com/oauth/authorize
?response_type=code
&client_id=leave-app
&redirect_uri=https://leave.example.com/oauth/callback
&scope=orchestra
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&state=$STATE
Marie signs in and approves. The browser returns to the app with a code, and the app exchanges it for tokens:
curl -X POST https://workflows.example.com/oauth/token \
-d grant_type=authorization_code \
-d client_id=leave-app \
-d code=$CODE \
-d redirect_uri=https://leave.example.com/oauth/callback \
-d code_verifier=$CODE_VERIFIER
The answer holds an access_token and a refresh_token. Keep both in the
keychain, never in plain storage.
Calling the API¶
Send the access token as the bearer, and nothing else about Marie.
Her tasks:
curl -X POST https://workflows.example.com/orchestra-api/tasks \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"limit": 20}'
Approving one, with the resume handle the list gave:
curl -X POST https://workflows.example.com/orchestra-api/step/resume \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"resume_handle": "'"$HANDLE"'", "payload": "approved"}'
Starting a request, which belongs to Marie:
curl -X POST https://workflows.example.com/orchestra-api/process/leave_request/start \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"variables": {"days": 3}}'
A call that adds an actor_proof or an initiator_proof is refused with a
400: the token already says who the person is, and it can name nobody else.
The operations that act for the caller alone, such as signaling a token, the
handshake and the job endpoints, refuse a person's token with a 403. The
OpenAPI document says which operations take one, in
x-orchestra-person-token.
Renewing the token¶
An access token lives as long as the consumer's Access token expiration time says. Once it has expired, Simple OAuth answers a call with a 401. Refresh it, then call again:
curl -X POST https://workflows.example.com/oauth/token \
-d grant_type=refresh_token \
-d client_id=leave-app \
-d refresh_token=$REFRESH_TOKEN
Each refresh hands back a new refresh token, and the old one is spent. When the refresh is refused, the person signs in again.
Following a request¶
A calling site is told when a request it started changes: it gives the workflow site an address to send notices to. An app has no such address. So it reads what it shows, when it shows it:
- Marie's work items, from
/orchestra-api/work-items: her tasks, and the operations she pulls, as her My actions page lists them. - The requests she started, from
/orchestra-api/requests, as her My requests page lists them. - One of those instances, as its page shows it to Marie, from
/orchestra-api/instance/{instance_id}/view, sent as a POST with the token: its status, the statuses it has passed through, the steps it waits on, and the data she submitted. She sees an instance she started or works on, and no other. - For a person granted Access Orchestra process instances, any instance
of the tenant and its variables, from
/orchestra-api/instance/{instance_id}, sent as a POST with the token.
Read them when the app opens, and when Marie pulls to refresh. To wake the app when a task arrives, the workflow site can send a push notification through a delivery channel of your own, next to email. Keep the notice to "something changed", and let the app read the rest from the API with Marie's token.