OpenID Connect

Bramble as an OpenID Connect provider ("Sign in with Bramble"): discovery, keys, authorise and consent, token (authorisation code with PKCE, refresh token, device code), device authorisation for apps without a browser, and user info. See single sign-on.

MethodPathSummary
GET/.well-known/openid-configurationOpenID Connect discovery
GET/auth/oidc/jwks.jsonSigning keys (JWKS)
GET/auth/oidc/authorizeStart sign-in for an app (authorise)
GET/auth/oidc/consent-infoWhat the consent screen should show
POST/auth/oidc/consentAllow or deny an app
POST/auth/oidc/tokenGet tokens
POST/auth/oidc/device_authorizationStart device sign-in
GET/auth/oidc/deviceUnder /oidc/ because BRM-Gateway proxies /oidc/* (and /auth/oidc/* on a tenant domain) on every host; a root /device isn't routed
GET/auth/oidc/device/infoWhat a device code is for
POST/auth/oidc/device/decisionApprove or deny a device
GET/auth/oidc/userinfoThe signed-in person's claims

#OpenID Connect discovery

GET/.well-known/openid-configuration
Address
https://<your-org>.brambleit.cloud/.well-known/openid-configuration
Requires
None: public, or protected by a token in the address or body.

Response 200

FieldTypeDescription
issuerany
authorization_endpointstring
token_endpointstring
userinfo_endpointstring
jwks_uristring
scopes_supportedarray of any
response_types_supportedarray of string
device_authorization_endpointstring
grant_types_supportedarray of string
subject_types_supportedarray of string
id_token_signing_alg_values_supportedarray of string
token_endpoint_auth_methods_supportedarray of string
code_challenge_methods_supportedarray of string
claims_supportedarray of string
Example response
JSON
{
  "issuer": null,
  "authorization_endpoint": "string",
  "token_endpoint": "…",
  "userinfo_endpoint": "string",
  "jwks_uri": "string",
  "scopes_supported": [],
  "response_types_supported": [
    "string"
  ],
  "device_authorization_endpoint": "string",
  "grant_types_supported": [
    "string"
  ],
  "subject_types_supported": [
    "Example subject types supported"
  ],
  "id_token_signing_alg_values_supported": [
    "…"
  ],
  "token_endpoint_auth_methods_supported": [
    "…"
  ],
  "code_challenge_methods_supported": [
    "string"
  ],
  "claims_supported": [
    "string"
  ]
}

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/.well-known/openid-configuration', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#Signing keys (JWKS)

GET/auth/oidc/jwks.json
Address
https://<your-org>.brambleit.cloud/auth/oidc/jwks.json
Requires
None: public, or protected by a token in the address or body.

Response 200

FieldTypeDescription
keysarray of any
Example response
JSON
{
  "keys": []
}

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/jwks.json', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#Start sign-in for an app (authorise)

GET/auth/oidc/authorize

The authorisation endpoint. Send the person here with client_id, redirect_uri, response_type=code, scope, state and (recommended) code_challenge + code_challenge_method=S256. After sign-in and consent, Bramble redirects back with code and state.

Address
https://<your-org>.brambleit.cloud/auth/oidc/authorize
Requires
None: public, or protected by a token in the address or body.

Query parameters

NameTypeDescription
client_idrequiredinteger
redirect_urirequiredany
response_typestring
scoperequiredstring
stateany
code_challengerequiredany
code_challenge_methodstring
nonceany

Response

A redirect, a file or page.

Errors

StatusMessage
400This app isn't set up correctly
400This app isn't registered
400This app has been disabled
403Single sign-on is turned off

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/authorize?client_id=7&redirect_uri=%E2%80%A6&scope=string&code_challenge=%E2%80%A6', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#Allow or deny an app

POST/auth/oidc/consent
Address
https://<your-org>.brambleit.cloud/auth/oidc/consent
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
decisionstringallow or deny.

Response

A body, a redirect.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/consent', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"decision":"string"}),
});
const data = await res.json();

#Get tokens

POST/auth/oidc/token

The token endpoint (form-encoded). grant_type is authorization_code (with code, redirect_uri, code_verifier), refresh_token (with refresh_token) or urn:ietf:params:oauth:grant-type:device_code (with device_code). Authenticate the app with client_id and client_secret. Returns access_token, id_token, expires_in and, where allowed, refresh_token.

Address
https://<your-org>.brambleit.cloud/auth/oidc/token
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
grant_typestring

Errors

StatusMessage
400invalid_client
401invalid_client
403access_denied
400unsupported_grant_type

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/token', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"grant_type":"string"}),
});
const data = await res.json();

#Start device sign-in

POST/auth/oidc/device_authorization

For apps without a browser. Returns a user_code to show and a verification_uri where the person enters it; then poll the token endpoint with the device_code.

Address
https://<your-org>.brambleit.cloud/auth/oidc/device_authorization
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
scopeany
device_namestring

Response 200

FieldTypeDescription
device_codeany
user_codeany
verification_uriany
verification_uri_completestring
expires_innumber
intervalarray of any
Example response
JSON
{
  "device_code": null,
  "user_code": null,
  "verification_uri": null,
  "verification_uri_complete": "string",
  "expires_in": 12.5,
  "interval": []
}

Errors

StatusMessage
400unauthorized_client
400invalid_client
400invalid_scope
401invalid_client

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/device_authorization', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"scope":"…","device_name":"Example device name"}),
});
const data = await res.json();

#Under /oidc/ because BRM-Gateway proxies /oidc/* (and /auth/oidc/* on a tenant domain) on every host; a root /device isn't routed

GET/auth/oidc/device

Under /oidc/ because BRM-Gateway proxies /oidc/* (and /auth/oidc/* on a tenant domain) on every host; a root /device isn't routed. Signed-out visitors go to /auth/, which is the login page on every gateway host.

Address
https://<your-org>.brambleit.cloud/auth/oidc/device
Requires
None: public, or protected by a token in the address or body.

Response

A redirect, a file or page.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/device', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#What a device code is for

GET/auth/oidc/device/info
Address
https://<your-org>.brambleit.cloud/auth/oidc/device/info
Requires
None: public, or protected by a token in the address or body.

Query parameters

NameTypeDescription
user_codeany

Response 200

FieldTypeDescription
clientNamestring
deviceNamestring
requestedIpany
requestedAtstring (date-time)
scopesarray of any
userobject
user.namestring
user.emailstring
Example response
JSON
{
  "clientName": "Example clientname",
  "deviceName": "Example devicename",
  "requestedIp": null,
  "requestedAt": "2026-11-12T09:30:00.000Z",
  "scopes": [],
  "user": {
    "name": "Example name",
    "email": "alex@example.com"
  }
}

Errors

StatusMessage
401Sign in first.
404That code isn't valid or has expired. Check the code on the device, or start again there.
403Your account isn't allowed to use …. Ask your administrator.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/device/info', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#Approve or deny a device

POST/auth/oidc/device/decision
Address
https://<your-org>.brambleit.cloud/auth/oidc/device/decision
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
user_codeany
decisionstring

Response 200

FieldTypeDescription
approvedany
Example response
JSON
{
  "approved": null
}

Errors

StatusMessage
415Unsupported request
401Sign in first.
404That code isn't valid or has expired.
403Your account isn't allowed to use ….
409This request was already answered.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/device/decision', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"user_code":"…","decision":"string"}),
});
const data = await res.json();

#The signed-in person's claims

GET/auth/oidc/userinfo

With the access token as Authorization: Bearer. Returns sub plus profile, email and groups claims for the scopes granted.

Address
https://<your-org>.brambleit.cloud/auth/oidc/userinfo
Requires
None: public, or protected by a token in the address or body.
Headers
authorization

Response 200

Errors

StatusMessage
401invalid_token

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/oidc/userinfo', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();