Skip to content

Authentication

This page shows you how to get an access token for Goiabada’s APIs and call them with it.

Goiabada has two APIs, both served by the auth server:

  • The Admin API, under /api/v1/admin/, manages users, groups, clients, resources, permissions and settings. It’s the API the admin console itself calls. A service calls it with a token of its own.
  • The Account API, under /api/v1/account/, lets a signed-in user manage their own account: profile, email, password, two-factor authentication, sessions and consents. Your app calls it with the user’s token.

Every operation is in the Admin API and Account API reference, with a curl example.

A service that manages Goiabada on its own, with no user present, gets its token through the client credentials flow: it signs in as a client, with the client’s own permissions.

  1. In the admin console, open Admin, Clients and click Create new. Enter a Client identifier, such as provisioning-service, turn on Client credentials flow, turn off Authorization code flow with PKCE, and click Create.

  2. Click Manage next to the new client, open Authentication and copy the Client secret.

  3. Open Permissions, choose the authserver resource and the permission your service needs, such as manage-users, click Grant permission and then Save. Scopes explains what each permission allows.

  4. Get an access token, asking for the scope that matches the permission:

    Terminal window
    curl -X POST https://auth.example.com/auth/token \
    -d grant_type=client_credentials \
    -d client_id=provisioning-service \
    -d client_secret=YOUR_CLIENT_SECRET \
    -d scope=authserver:manage-users

    The answer carries the token in access_token, and its lifetime in seconds in expires_in.

  5. Call the API with the token in the Authorization header:

    Terminal window
    curl "https://auth.example.com/api/v1/admin/users/search?query=ana" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Ask for several scopes at once by separating them with spaces, as in scope=authserver:manage-users authserver:manage-clients. When the token expires, request a new one the same way.

Each of manage, admin-read, manage-users, manage-clients and manage-settings makes the client an administrator, so only an administrator holding authserver:manage can grant one. The administrator the setup created holds it.

The Account API acts on the account of the user the token was issued for, so it needs a token your app got for a signed-in user, through the authorization code flow. A token from the client credentials flow is refused, as below.

  1. Send the user to the authorization endpoint, asking for authserver:manage-account beside openid:

    GET https://auth.example.com/auth/authorize?client_id=my-app
    &redirect_uri=https://my-app.example.com/callback
    &response_type=code
    &scope=openid%20authserver:manage-account
    &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
    &code_challenge_method=S256
    &state=abc123

    The first time, the user sees the consent screen, which names your app and says what authserver:manage-account lets it do, and approves it. Their answer is kept, so each user is asked once.

  2. Exchange the code the user comes back with for tokens:

    Terminal window
    curl -X POST https://auth.example.com/auth/token \
    -d grant_type=authorization_code \
    -d client_id=my-app \
    -d client_secret=YOUR_CLIENT_SECRET \
    -d code=THE_CODE \
    -d redirect_uri=https://my-app.example.com/callback \
    -d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  3. Call the API with the access_token from the answer:

    Terminal window
    curl https://auth.example.com/api/v1/account/profile \
    -H "Authorization: Bearer USER_ACCESS_TOKEN"

Every user holds the manage-account permission from the moment they’re created, and it isn’t an administrative permission, so your client needs no permission of its own, and no allowance, to ask for it. What it needs is the user’s approval. The consent screen is shown the first time each user signs in to your app asking for authserver:manage-account, whatever your client’s Consent required says, and again if they untick it or withdraw it under Account, Manage consents. Add sign-in to a web app covers the sign-in itself, PKCE included.

Until a user has approved it, your app can’t get the scope without them:

  • a prompt=none request asking for it is answered consent_required;
  • a refresh renewing it is refused with invalid_grant, and the refresh token isn’t spent, so a refresh whose scope leaves authserver:manage-account out still works.

A session or a live refresh token from an earlier authorization-code sign-in can encounter these checks until the user approves the scope. Send the user through an ordinary sign-in: they approve once, and the refreshes and silent requests after it work. Refresh tokens from the password grant don’t need this consent.

There are three ways, and which one fits depends on who’s making the change:

  • The Account API, with the user’s token. For an app that changes the signed-in user’s own account on their behalf. It asks for authserver:manage-account as above, and each user approves that once on the consent screen. The token reaches that user’s account and nothing else.
  • The Admin API, with your client’s own token. For a service that changes users’ accounts with no user present, such as provisioning from an HR system. It gets its token through the client credentials flow, as in Call the Admin API, holding authserver:manage-users, which only an administrator with authserver:manage can grant it. That makes the client an administrator, and it still can’t change an administrator.
  • A link to the admin console’s Account pages. For a change the user makes themselves. Link to /account/profile on the admin console, such as https://admin.example.com/account/profile. The user signs in there if they need to and edits their profile, email, phone, address, picture, password, two-factor authentication, sessions and consents. Your app needs no scope at all.

Every operation under /api/v1/ takes an access token in the Authorization header, as Bearer followed by the token. The reference also lists one public operation outside /api/v1/, the client logo, GET /client/logo/{clientIdentifier}, which takes none.

The token must be an access token the auth server issued for the authserver resource, which is what asking for an authserver: scope gets you. An ID token or a refresh token is refused.

The Admin API accepts a client’s token from the client credentials flow, and a user’s token from the authorization code flow when the client may request the administrative scopes on a user’s behalf.

The Account API accepts only a token issued for a user. A token from the client credentials flow is answered 403: with INSUFFICIENT_SCOPE when it doesn’t carry authserver:manage-account, which is the usual case, and with USER_CONTEXT_REQUIRED when its client was granted that permission.

A user’s token is checked against the user and their session on every call, so it stops working before it expires once the user is disabled or the session it was issued through ends. The call is then answered 401 with INVALID_TOKEN.

The other refusals:

Answer Error code Why
401 ACCESS_TOKEN_REQUIRED No token was sent.
401 INVALID_TOKEN The token is malformed, expired, not an access token for authserver, or its user or session is gone.
403 INSUFFICIENT_SCOPE The token carries none of the scopes the operation accepts.

The errors page has the format of every error answer.

The auth server serves the whole API as an OpenAPI 3.0 document at /openapi.yaml, with no authentication:

Terminal window
curl https://auth.example.com/openapi.yaml

This reference is generated from it, so the two always say the same thing. Import it into Postman or Swagger UI, or generate a client from it.