Developer console

Монгол  ·  English

Connecting your own system to Gerege Nexus. The platform is itself an OAuth2 / OpenID Connect provider: your application signs a person in here, then calls the API on their behalf.


1. Register a client

Install the Developer module in your workspace and open /developer/apps. Press New app and supply:

Field Meaning
Name Shown to the person on the consent screen
redirect_uris Required. At least one. Absolute, no fragment (#). Everything except loopback (http://127.0.0.1:…) must be HTTPS
Public client Yes for a browser or mobile app — no secret, proved by PKCE

Registration returns a client_id. The client_secret appears in that one response only — there is no API that reads it back. If you lose it, or it leaks, you do not need to register a new client: use the rotate action in §6, Managing a client, below.

Why: an API that hands the secret back would be the easiest way to steal it. Showing it once and keeping only a digest means that even somebody who reads the database cannot recover it.


2. The sign-in flow

Standard authorization code + PKCE. No other grant is offered — not implicit, not password.

2.1 Prepare a verifier

code_verifier  = a random string of 43–128 characters
code_challenge = BASE64URL( SHA256( code_verifier ) )

code_challenge_method must be S256. plain is refused, since it would make PKCE pointless.

2.2 Send the person

GET https://cloud.gerege.mn/openerp/oauth2/auth
  ?response_type=code
  &client_id=<your client_id>
  &redirect_uri=<exactly as registered>
  &scope=openid profile email
  &state=<random, against CSRF>
  &code_challenge=<from above>
  &code_challenge_method=S256

The platform reads its own session:

  • not signed in → the sign-in screen;
  • no workspace chosen → the chooser;
  • consent missing → the consent screen;
  • everything settled → back to redirect_uri?code=…&state=…

redirect_uri must match what you registered exactly — no prefix matching, no ignoring the query string. An error found before the client and redirect were verified is never bounced to that address; the platform reports it on its own page, which is what stops it being an open redirect.

2.3 Exchange the code

POST https://cloud.gerege.mn/openerp/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<the returned code>
&redirect_uri=<the same address>
&client_id=<client_id>
&code_verifier=<the original verifier>

A confidential client also sends client_secret, in the form or via HTTP Basic.

The response:

{
  "access_token": "…",
  "refresh_token": "…",
  "id_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600
}

A code is single-use and lives 60 seconds. Presenting it twice revokes every refresh token descended from it — a replay means a copy is in somebody else's hands, so the whole chain goes.


3. Using the tokens

GET https://cloud.gerege.mn/openerp/api/v1/contacts
Authorization: Bearer <access_token>
  • id_token is a JWT signed RS256. Verify it against the public key at /.well-known/jwks.json.
  • access_token is opaque — do not try to parse it. Ask /oauth2/introspect whether it is valid.
  • refresh_token rotates: each refresh issues a new one. Presenting a spent one revokes the entire chain.

Scopes

Scope What it opens
openid Receive an id_token; required to call UserInfo
profile Name and workspace
email E-mail address
erp.read Read business data
erp.write Write business data

A scope never widens what the person may already do. Granting erp.write to your app does not let it edit invoices for somebody who may not edit invoices.


4. UserInfo

GET /openerp/oauth2/userinfo
Authorization: Bearer <access_token>

The claims returned are limited to the scopes that were granted. Without openid the call is refused.


5. Revoking

POST /openerp/oauth2/revoke     (client authentication required)
POST /openerp/oauth2/introspect (RFC 7662, client authentication required)

A person can also withdraw consent themselves, which kills your refresh tokens along with it.


6. Managing a client

Each client on /developer/apps carries four actions. All of them are bounded by the workspace that registered it — naming another workspace's client_id is answered exactly the way naming a client that does not exist is.

6.1 Edit — PATCH /api/v1/developer/apps/{client_id}

The name, the redirect_uris and the scopes can be changed.

The redirect URIs are the reason this matters most: one wrong entry and your app can sign nobody in. They are checked with exactly the rule registration uses — absolute, no fragment (#), HTTPS outside loopback — and a bad one is refused rather than tidied up, because a redirect URI silently rewritten is a redirect URI you did not register.

grant_types and token_endpoint_auth_method cannot be edited. Turning a confidential client public, or back, changes how every token already in circulation was proved; the honest way to make that change is to register a different client and move to it.

6.2 Rotate the secret — POST /api/v1/developer/apps/{client_id}/secret

A new secret is generated and shown once, in that response. The old one stops working at that moment, on every replica, because every client read is a database query rather than a cached copy.

Access and refresh tokens already issued keep working. That is deliberate: the secret authenticates the client at the token endpoint; it is not a key those tokens were derived from. They were issued to the same client, for the same people, under consents nobody has withdrawn. A rotation that also signed every user out would be one an operator hesitates to perform at three in the morning, and the hesitation is the vulnerability.

When your judgement is that the tokens are compromised too, that is a different action: disable the client (below), or withdraw one person's consent.

A public client holds no secret, so this action answers 400 — PKCE is its proof.

6.3 Disable and enable — POST .../disable, POST .../enable

A disabled client authenticates nowhere — not at /oauth2/token, not at /oauth2/auth, not at introspection or revocation — and every access and refresh token it holds is revoked.

But nothing is deleted: the token, consent and authorization-code rows stay where they are, so the audit trail outlives the decision to stop the client. The consents stay too — whether to let an app act for them is the person's decision, not an administrator's, so switching the client back on does not ask everybody again. The revoked tokens do not come back; the app collects a fresh set at the next sign-in.

6.4 Delete — DELETE /api/v1/developer/apps/{client_id}

Only a client that was never used can be deleted. If it has ever held a token, a refresh token, a consent or an authorization code, the server answers 409.

Why: three of those tables cascade on the client row, so deleting it would erase the record of what the client did; the fourth (oauth2_tokens) does not point at it at all, so its rows would simply be orphaned and go on working until they expired. Neither is an acceptable way to take a credential back. Delete is for the registration made by mistake — wrong name, wrong redirect, five minutes old, nothing attached to it.


7. Publishing as an external app

To appear in the app store, declare kind: "external" in your manifest. Installing then:

  • creates the OAuth2 client from the manifest automatically (public, PKCE);
  • creates the webhook subscription inside the install transaction, so "installed" and "subscribed" can never disagree;
  • adds a sidebar entry that opens your app.

Webhooks arrive HMAC-signed. Reinstalling does not rotate the secret — if it did, a secret you had already stored would be silently invalidated.

Such a client is managed by its manifest: it belongs to no workspace, so it never appears on the /developer/apps screen and none of the §6 actions can reach it. To change its redirect URIs or scopes, change the manifest — an edit made from the screen would be undone at the next boot. If you need a confidential client, register one through the developer console, where there is somebody to hand the secret to.

See the module authoring guide.


8. Error responses

Status Meaning
400 Bad request — mismatched redirect_uri, missing PKCE, code_challenge_method other than S256
401 Token missing, expired or revoked
403 Not permitted — app not installed, or the person lacks that permission
409 Conflict — a code that has already been used, or an attempt to delete a client that has been used (§6.4)
429 Too many attempts
502 · 503 Upstream trouble. 503 carries Retry-After and means coming back is worth it; 502 means somebody has to fix something

The full list is in the API reference.