Endpoints
Discovery endpoint
Section titled “Discovery endpoint”You can find a link to the well-known discovery URL by going to the root of the admin console. The URL will look like this:
https://auth.example.com/.well-known/openid-configurationThis endpoint shows the capabilities supported by Goiabada.
/auth/authorize (GET)
Section titled “/auth/authorize (GET)”The authorize endpoint is used to request authorization codes via the browser. This process normally involves authentication of the end-user and optionally obtaining consent.
Parameters
Section titled “Parameters”| Parameter | Required | Description |
|---|---|---|
client_id | Yes | The client identifier. |
redirect_uri | Yes | The redirect URI is the callback entry point of the app. This must exactly match one of the allowed redirect URIs for the client. |
response_type | Yes |
|
code_challenge_method | Conditional | S256 is the only value supported. Required when PKCE is enforced (see PKCE configuration). |
code_challenge | Conditional | A random string between 43 and 128 characters long. Required when PKCE is enforced. |
scope | Yes | One or more registered scopes, separated by a space. A scope can be either a resource:permission or an OIDC scope. Must include openid for OpenID Connect flows. |
response_mode | No | Supported values: query (default for code flow), fragment (default and required for implicit flow), or form_post. |
state | Recommended | Any string. Goiabada will echo back the state value on the token response, for CSRF/replay protection. |
nonce | Conditional | Any string. Goiabada will echo back the nonce value in the ID token, as a claim, for replay protection. Required when using implicit flow with id_token. |
max_age | No | If the user’s authentication timestamp exceeds the max age (in seconds), they will have to re-authenticate. |
acr_values | No | Supported values: urn:goiabada:level1, urn:goiabada:level2_optional or urn:goiabada:level2_mandatory. |
Example
Section titled “Example”GET /auth/authorize? client_id=my-app& redirect_uri=https://my-app.com/callback& response_type=code& scope=openid profile email& code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM& code_challenge_method=S256& state=abc123& nonce=xyz789/auth/token (POST)
Section titled “/auth/token (POST)”The token endpoint serves the purpose of requesting tokens. This can happen either through the authorization code flow (exchanging an authorization code for tokens), the client credentials flow (client directly requests tokens), or using a refresh token.
Client authentication
Section titled “Client authentication”Confidential clients must authenticate when calling the token endpoint. Goiabada supports two authentication methods:
| Method | Description |
|---|---|
client_secret_post | Send client_id and client_secret as form parameters in the HTTP request body |
client_secret_basic | Send credentials via HTTP Basic authentication header |
Parameters
Section titled “Parameters”| Parameter | Description |
|---|---|
grant_type | authorization_code, client_credentials, or refresh_token |
client_id | The client identifier. |
client_secret | The client secret, if it’s a confidential client. |
redirect_uri | Required for the authorization_code grant type. |
code | The authorization code. Required for authorization_code grant type. |
code_verifier | The original string from which the code_challenge was derived. Required if PKCE was used in the authorization request. |
scope | For client_credentials: required, one or more resource:permission scopes. For refresh_token: optional, to restrict the original scope. |
refresh_token | Required for the refresh_token grant type. |
Example: Authorization code exchange
Section titled “Example: Authorization code exchange”curl -X POST https://auth.example.com/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "client_id=my-app" \ -d "client_secret=my-secret" \ -d "code=SplxlOBeZQQYbYS6WxSbIA" \ -d "redirect_uri=https://my-app.com/callback" \ -d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"Example: Client credentials
Section titled “Example: Client credentials”curl -X POST https://auth.example.com/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=my-service" \ -d "client_secret=service-secret" \ -d "scope=product-api:read product-api:write"Example: Using HTTP Basic authentication
Section titled “Example: Using HTTP Basic authentication”Instead of sending credentials in the request body, you can use the Authorization header with Basic authentication. The header value is Basic followed by the Base64 encoding of client_id:client_secret:
# Base64 of "my-service:service-secret" is "bXktc2VydmljZTpzZXJ2aWNlLXNlY3JldA=="curl -X POST https://auth.example.com/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Authorization: Basic bXktc2VydmljZTpzZXJ2aWNlLXNlY3JldA==" \ -d "grant_type=client_credentials" \ -d "scope=product-api:read product-api:write"Or using curl’s -u shorthand which does the encoding automatically:
curl -X POST https://auth.example.com/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -u "my-service:service-secret" \ -d "grant_type=client_credentials" \ -d "scope=product-api:read product-api:write"Successful response
Section titled “Successful response”A successful token response includes the following headers per RFC 6749 Section 5.1:
HTTP/1.1 200 OKContent-Type: application/jsonCache-Control: no-storePragma: no-cacheThe response body contains the tokens:
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 300, "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "scope": "openid profile email"}Error responses
Section titled “Error responses”Error responses follow RFC 6749 Section 5.2 and include cache prevention headers:
HTTP/1.1 400 Bad RequestContent-Type: application/jsonCache-Control: no-storePragma: no-cacheFor client authentication failures, the response uses HTTP 401 status code with a WWW-Authenticate header when HTTP Basic authentication was attempted:
HTTP/1.1 401 UnauthorizedContent-Type: application/jsonCache-Control: no-storePragma: no-cacheWWW-Authenticate: BasicThe response body contains error details:
{ "error": "invalid_client", "error_description": "Client authentication failed. Please review your client_secret."}| Error code | HTTP status | Description |
|---|---|---|
invalid_request | 400 | Missing required parameter, invalid parameter value, or malformed request. |
invalid_client | 401 | Client authentication failed (unknown client, no credentials, or invalid secret). |
invalid_grant | 400 | Authorization code or refresh token is invalid, expired, revoked, or doesn’t match the redirect URI. |
unauthorized_client | 400 | Client is not authorized for this grant type. |
unsupported_grant_type | 400 | The grant type is not supported. |
invalid_scope | 400 | The requested scope is invalid, unknown, or exceeds the scope granted. |
/auth/logout (GET or POST)
Section titled “/auth/logout (GET or POST)”This endpoint enables the client application to initiate a logout. This implementation aligns with the OpenID Connect RP-Initiated Logout 1.0 protocol.
Basic logout (no parameters)
Section titled “Basic logout (no parameters)”- GET /auth/logout - Displays a logout consent screen, prompting the user to confirm their intention to log out. No redirection occurs.
- POST /auth/logout - Immediately logs out the user and redirects to the auth server base URL.
RP-initiated logout (with parameters)
Section titled “RP-initiated logout (with parameters)”For proper logout with redirection back to your application, use either GET or POST with the following parameters:
| Parameter | Required | Description |
|---|---|---|
id_token_hint | Yes | The previously issued ID token (can be encrypted or unencrypted). |
post_logout_redirect_uri | Yes | A redirect URI that must be pre-registered with the client. |
client_id | Conditional | Required only if id_token_hint is encrypted with the client secret. |
state | No | Any arbitrary string that will be echoed back in the redirect. |
Response
Section titled “Response”After successful logout, the user is redirected to the post_logout_redirect_uri with the following query parameters:
sid- The session identifier from the ID tokenstate- The state parameter if provided in the request
Example redirect: https://your-app.com/logged-out?sid=abc123&state=xyz
Encrypting the ID token hint
Section titled “Encrypting the ID token hint”Encrypting the id_token_hint enhances security by preventing exposure of the ID token in browser history and logs.
private static string AesGcmEncryption(string idTokenUnencrypted, string clientSecret){ var key = new byte[32];
// use the first 32 bytes of the client secret as key var keyBytes = Encoding.UTF8.GetBytes(clientSecret); Array.Copy(keyBytes, key, Math.Min(keyBytes.Length, key.Length));
// random nonce var nonce = new byte[AesGcm.NonceByteSizes.MaxSize]; // MaxSize = 12 RandomNumberGenerator.Fill(nonce);
using var aes = new AesGcm(key); var cipherText = new byte[idTokenUnencrypted.Length]; var tag = new byte[AesGcm.TagByteSizes.MaxSize]; // MaxSize = 16 aes.Encrypt(nonce, Encoding.UTF8.GetBytes(idTokenUnencrypted), cipherText, tag);
// concatenate nonce (12 bytes) + ciphertext (? bytes) + tag (16 bytes) var encrypted = new byte[nonce.Length + cipherText.Length + tag.Length]; Array.Copy(nonce, encrypted, nonce.Length); Array.Copy(cipherText, 0, encrypted, nonce.Length, cipherText.Length); Array.Copy(tag, 0, encrypted, nonce.Length + cipherText.Length, tag.Length);
return Convert.ToBase64String(encrypted);}import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "io" "math")
func AesGcmEncryption(idTokenUnencrypted string, clientSecret string) (string, error) { key := make([]byte, 32)
// Use the first 32 bytes of the client secret as key keyBytes := []byte(clientSecret) copy(key, keyBytes[:int(math.Min(float64(len(keyBytes)), float64(len(key))))])
// Random nonce nonce := make([]byte, 12) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", err }
block, err := aes.NewCipher(key) if err != nil { return "", err }
aesGcm, err := cipher.NewGCM(block) if err != nil { return "", err }
cipherText := aesGcm.Seal(nil, nonce, []byte(idTokenUnencrypted), nil)
// Concatenate nonce (12 bytes) + ciphertext (? bytes) + tag (16 bytes) encrypted := make([]byte, len(nonce)+len(cipherText)) copy(encrypted, nonce) copy(encrypted[len(nonce):], cipherText)
return base64.StdEncoding.EncodeToString(encrypted), nil}const crypto = require('crypto');
function aesGcmEncryption(idTokenUnencrypted, clientSecret) { const key = Buffer.alloc(32);
// Use the first 32 bytes of the client secret as the key const keyBytes = Buffer.from(clientSecret, 'utf-8'); keyBytes.copy(key, 0, 0, Math.min(keyBytes.length, key.length));
// Random nonce const nonce = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce); let cipherText = cipher.update(idTokenUnencrypted, 'utf-8', 'base64'); cipherText += cipher.final('base64');
const tag = cipher.getAuthTag();
// Concatenate nonce (12 bytes) + ciphertext (? bytes) + tag (16 bytes) const encrypted = Buffer.concat([nonce, Buffer.from(cipherText, 'base64'), tag]);
return encrypted.toString('base64');}/userinfo (GET or POST)
Section titled “/userinfo (GET or POST)”The UserInfo endpoint is an OpenID Connect standard endpoint used to retrieve identity information about an authenticated user.
Authentication
Section titled “Authentication”Send a valid access token using the Authorization header:
Authorization: Bearer <access-token>The endpoint requires the authserver:userinfo scope to be present in the access token.
Response
Section titled “Response”The response is a JSON object containing user claims. The sub (subject) claim is always included. Additional claims depend on the scopes in the access token:
| Scope in access token | Claims returned |
|---|---|
profile | name, given_name, middle_name, family_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at |
email | email, email_verified |
address | address (structured claim) |
phone | phone_number, phone_number_verified |
groups | groups (array of group identifiers configured to be included in ID tokens) |
attributes | attributes (map of user and group attributes configured to be included in ID tokens) |
Example response
Section titled “Example response”Request with scopes profile and email:
{ "sub": "248289761001", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "preferred_username": "j.doe", "email_verified": true, "profile": "https://auth.example.com/account/profile", "picture": "https://auth.example.com/userinfo/picture/248289761001", "updated_at": 1311280970}Error responses
Section titled “Error responses”- 401 Unauthorized - Missing or invalid access token
- 403 Forbidden - Access token lacks
authserver:userinfoscope - 500 Internal Server Error - User account is disabled or not found