Cygnus cloud service
Identity login API
POST /api/v1/identity/login requires a valid machine JWT with the
identity.login scope. The JWT must carry client_id and installation_id;
both must equal the values inside the encrypted payload.
The request uses a hybrid encrypted envelope:
{
"keyId": "cygnus-login-2026-01",
"encryptedKey": "base64 RSA-OAEP-SHA256 encrypted AES key",
"initializationVector": "base64 12-byte AES-GCM IV",
"encryptedPayload": "base64 AES-GCM ciphertext and tag"
}
The AES-GCM additional authenticated data is the UTF-8 keyId. The decrypted
JSON is:
{
"loginId": "user",
"password": "password",
"clientId": "client-id-from-jwt",
"installationId": "installation-id-from-jwt",
"nonce": "unique-random-value",
"issuedAt": "2026-07-23T06:30:00Z"
}
Configure the PKCS#8 RSA private key with
CYGNUS_LOGIN_PRIVATE_KEY=file:/secure/path/login-private.pem. Keep this key
outside the source tree and container image. The corresponding public key is
distributed to the on-prem gateway.
The database bootstrap is
src/main/resources/db/identity/001_identity_login_schema.sql. It is
transactional and idempotent; it copies login/menu data from matrix.public
to matrix.identity. It is intended for initial migration and controlled
development refreshes. Do not run it after identity becomes the production
system of record because its upserts intentionally refresh rows from public.
Machine token endpoint
POST /oauth2/token implements the client-credentials flow used by the
on-premises gateway. The client assertion must be:
- an inner RS256 JWT signed with the installation private key;
- encrypted as RSA-OAEP-256 plus AES-256-GCM using the cloud assertion key;
- bound to the configured client ID, installation ID, and token audience;
- unexpired and no longer-lived than
CYGNUS_ASSERTION_TTL.
The endpoint returns a short-lived RS256 access token carrying client_id,
installation_id, and the approved scope. The identity endpoint requires the
identity.login scope and verifies the same machine binding in the encrypted
login payload.
Generate separate cloud key pairs:
mkdir -p config/keys
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
-out config/keys/assertion-decryption-private.pem
openssl pkey -in config/keys/assertion-decryption-private.pem -pubout \
-out config/keys/assertion-decryption-public.pem
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
-out config/keys/access-token-private.pem
openssl pkey -in config/keys/access-token-private.pem -pubout \
-out config/keys/access-token-public.pem
chmod 600 config/keys/*private.pem
Configure clients in an external Spring YAML file rather than the packaged
application.yml:
cygnus:
security:
enabled: true
issuer-uri: https://cloud.example.com
audience: cygnus-cloud-api
token-audience: https://cloud.example.com/oauth2/token
clients:
customer-a:
enabled: true
installation-id: site-01
assertion-public-key: file:/secure/clients/customer-a/public.pem
scopes:
- identity.login
Start with that protected file using
--spring.config.additional-location=file:/secure/cygnus/clients.yml.
Never place cloud private keys, customer assertions, or installation private
keys in the repository or container image.