Skip to content

Endpoints

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-configuration

This endpoint shows the capabilities supported by Goiabada.

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.

ParameterRequiredDescription
client_idYesThe client identifier.
redirect_uriYesThe redirect URI is the callback entry point of the app. This must exactly match one of the allowed redirect URIs for the client.
response_typeYes

code for the authorization code flow with PKCE (recommended).
token, id_token, or id_token token for the implicit flow (legacy, must be enabled).

code_challenge_methodConditionalS256 is the only value supported. Required when PKCE is enforced (see PKCE configuration).
code_challengeConditionalA random string between 43 and 128 characters long. Required when PKCE is enforced.
scopeYesOne 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_modeNoSupported values: query (default for code flow), fragment (default and required for implicit flow), or form_post.
stateRecommendedAny string. Goiabada will echo back the state value on the token response, for CSRF/replay protection.
nonceConditionalAny 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_ageNoIf the user’s authentication timestamp exceeds the max age (in seconds), they will have to re-authenticate.
acr_valuesNoSupported values: urn:goiabada:level1, urn:goiabada:level2_optional or urn:goiabada:level2_mandatory.
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

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.

Confidential clients must authenticate when calling the token endpoint. Goiabada supports two authentication methods:

MethodDescription
client_secret_postSend client_id and client_secret as form parameters in the HTTP request body
client_secret_basicSend credentials via HTTP Basic authentication header
ParameterDescription
grant_typeauthorization_code, client_credentials, or refresh_token
client_idThe client identifier.
client_secretThe client secret, if it’s a confidential client.
redirect_uriRequired for the authorization_code grant type.
codeThe authorization code. Required for authorization_code grant type.
code_verifierThe original string from which the code_challenge was derived. Required if PKCE was used in the authorization request.
scopeFor client_credentials: required, one or more resource:permission scopes. For refresh_token: optional, to restrict the original scope.
refresh_tokenRequired for the refresh_token grant type.
Terminal window
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"
Terminal window
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"

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:

Terminal window
# 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:

Terminal window
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"

A successful token response includes the following headers per RFC 6749 Section 5.1:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

The 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 follow RFC 6749 Section 5.2 and include cache prevention headers:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

For 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 Unauthorized
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
WWW-Authenticate: Basic

The response body contains error details:

{
"error": "invalid_client",
"error_description": "Client authentication failed. Please review your client_secret."
}
Error codeHTTP statusDescription
invalid_request400Missing required parameter, invalid parameter value, or malformed request.
invalid_client401Client authentication failed (unknown client, no credentials, or invalid secret).
invalid_grant400Authorization code or refresh token is invalid, expired, revoked, or doesn’t match the redirect URI.
unauthorized_client400Client is not authorized for this grant type.
unsupported_grant_type400The grant type is not supported.
invalid_scope400The requested scope is invalid, unknown, or exceeds the scope granted.

This endpoint enables the client application to initiate a logout. This implementation aligns with the OpenID Connect RP-Initiated Logout 1.0 protocol.

  • 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.

For proper logout with redirection back to your application, use either GET or POST with the following parameters:

ParameterRequiredDescription
id_token_hintYesThe previously issued ID token (can be encrypted or unencrypted).
post_logout_redirect_uriYesA redirect URI that must be pre-registered with the client.
client_idConditionalRequired only if id_token_hint is encrypted with the client secret.
stateNoAny arbitrary string that will be echoed back in the redirect.

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 token
  • state - 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 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);
}

The UserInfo endpoint is an OpenID Connect standard endpoint used to retrieve identity information about an authenticated user.

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.

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 tokenClaims returned
profilename, given_name, middle_name, family_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at
emailemail, email_verified
addressaddress (structured claim)
phonephone_number, phone_number_verified
groupsgroups (array of group identifiers configured to be included in ID tokens)
attributesattributes (map of user and group attributes configured to be included in ID tokens)

Request with scopes profile and email:

{
"sub": "248289761001",
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"preferred_username": "j.doe",
"email": "[email protected]",
"email_verified": true,
"profile": "https://auth.example.com/account/profile",
"picture": "https://auth.example.com/userinfo/picture/248289761001",
"updated_at": 1311280970
}
  • 401 Unauthorized - Missing or invalid access token
  • 403 Forbidden - Access token lacks authserver:userinfo scope
  • 500 Internal Server Error - User account is disabled or not found