Skip to main content
The CIBA manager provides methods for implementing Client-Initiated Backchannel Authentication (CIBA) flows.

Overview

CIBA is an authentication flow that allows a client application to initiate authentication for a user through a backchannel, without requiring the user to interact directly with the client. This is useful for scenarios like:
  • Push notifications for authentication
  • Decoupled authentication flows
  • Financial transaction approvals

Methods

Initiate

Initiates a CIBA authentication request.
func (c *CIBA) Initiate(
    ctx context.Context,
    body ciba.Request,
    opts ...RequestOption,
) (*ciba.Response, error)
ctx
context.Context
required
The context for the request
body
ciba.Request
required
The CIBA request parameters
opts
...RequestOption
Optional request options

Request

login_hint
map[string]string
required
Contains format, iss (issuer), and sub (subject) for the user
scope
string
required
The scope for the flow (e.g., “openid profile”)
binding_message
string
required
A human-readable message shown to the user during authentication
audience
string
The unique identifier of the target API
client_id
string
Client ID (uses default if not provided)
client_secret
string
Client Secret (uses default if not provided)

Example

import (
    "context"
    "github.com/auth0/go-auth0/v2/authentication/ciba"
)

response, err := auth.CIBA.Initiate(
    ctx,
    ciba.Request{
        LoginHint: map[string]string{
            "format": "iss_sub",
            "iss":    "https://your-domain.auth0.com/",
            "sub":    "auth0|user123",
        },
        Scope:          "openid profile email",
        BindingMessage: "Approve login to your account",
        Audience:       "https://api.example.com",
    },
)
if err != nil {
    log.Fatalf("Failed to initiate CIBA: %v", err)
}

fmt.Printf("Auth Request ID: %s\n", response.AuthReqID)
fmt.Printf("Expires In: %d seconds\n", response.ExpiresIn)
fmt.Printf("Polling Interval: %d seconds\n", response.Interval)

Response

auth_req_id
string
The authentication request identifier to use for polling
expires_in
int64
The number of seconds the auth_req_id is valid for
interval
int64
The minimum number of seconds to wait between polling requests

Complete CIBA Flow Example

The following example demonstrates a complete CIBA flow, including initiating the request and polling for the token:
package main

import (
    "context"
    "fmt"
    "log"
    "time"
    
    "github.com/auth0/go-auth0/v2/authentication"
    "github.com/auth0/go-auth0/v2/authentication/ciba"
    "github.com/auth0/go-auth0/v2/authentication/oauth"
)

func main() {
    ctx := context.Background()
    
    // Initialize the auth client
    auth, err := authentication.New(
        ctx,
        "your-domain.auth0.com",
        authentication.WithClientID("your-client-id"),
        authentication.WithClientSecret("your-client-secret"),
    )
    if err != nil {
        log.Fatal(err)
    }
    
    // Step 1: Initiate CIBA request
    cibaResponse, err := auth.CIBA.Initiate(
        ctx,
        ciba.Request{
            LoginHint: map[string]string{
                "format": "iss_sub",
                "iss":    "https://your-domain.auth0.com/",
                "sub":    "auth0|user123",
            },
            Scope:          "openid profile email",
            BindingMessage: "Approve login to your account",
            Audience:       "https://api.example.com",
        },
    )
    if err != nil {
        log.Fatalf("Failed to initiate CIBA: %v", err)
    }
    
    fmt.Printf("CIBA initiated. Auth Request ID: %s\n", cibaResponse.AuthReqID)
    fmt.Printf("Polling interval: %d seconds\n", cibaResponse.Interval)
    
    // Step 2: Poll for token
    // Note: In production, you would use the token endpoint with grant_type=urn:openid:params:grant-type:ciba
    ticker := time.NewTicker(time.Duration(cibaResponse.Interval) * time.Second)
    defer ticker.Stop()
    
    timeout := time.After(time.Duration(cibaResponse.ExpiresIn) * time.Second)
    
    for {
        select {
        case <-timeout:
            log.Fatal("CIBA request timed out")
            
        case <-ticker.C:
            // Poll the token endpoint
            tokens, err := auth.OAuth.LoginWithGrant(
                ctx,
                "urn:openid:params:grant-type:ciba",
                map[string][]string{
                    "auth_req_id": {cibaResponse.AuthReqID},
                },
                oauth.IDTokenValidationOptions{},
            )
            
            if err != nil {
                // Check if it's an authorization_pending error
                if aerr, ok := err.(*authentication.Error); ok {
                    if aerr.Err == "authorization_pending" {
                        fmt.Println("Still waiting for user approval...")
                        continue
                    } else if aerr.Err == "slow_down" {
                        // Increase polling interval
                        ticker.Reset(time.Duration(cibaResponse.Interval+5) * time.Second)
                        continue
                    }
                }
                log.Fatalf("Failed to get token: %v", err)
            }
            
            // Success!
            fmt.Println("\nAuthentication successful!")
            fmt.Println("Access Token:", tokens.AccessToken)
            fmt.Println("ID Token:", tokens.IDToken)
            return
        }
    }
}

Understanding CIBA Parameters

Login Hint

The login_hint parameter identifies the user to authenticate. It must contain:
  • format: The format of the hint (typically “iss_sub”)
  • iss: The issuer identifier (your Auth0 domain)
  • sub: The user’s subject identifier (user ID)
loginHint := map[string]string{
    "format": "iss_sub",
    "iss":    "https://your-domain.auth0.com/",
    "sub":    "auth0|user123",
}

Binding Message

The binding message is displayed to the user during authentication. It should be a clear, human-readable message that explains what the user is approving.
bindingMessage := "Approve payment of $100 to Merchant XYZ"

Error Handling

response, err := auth.CIBA.Initiate(ctx, request)
if err != nil {
    if aerr, ok := err.(*authentication.Error); ok {
        switch aerr.StatusCode {
        case 400:
            fmt.Println("Bad request:", aerr.Message)
        case 401:
            fmt.Println("Unauthorized:", aerr.Message)
        case 403:
            fmt.Println("User not enrolled in CIBA:", aerr.Message)
        default:
            fmt.Printf("API error: %s\n", aerr.Message)
        }
    } else {
        log.Fatalf("Request failed: %v", err)
    }
}

Polling Errors

When polling for tokens, you may receive the following errors:
  • authorization_pending: The user hasn’t approved the request yet. Continue polling.
  • slow_down: You’re polling too frequently. Increase the polling interval by at least 5 seconds.
  • expired_token: The auth_req_id has expired. You need to initiate a new CIBA request.
  • access_denied: The user denied the authentication request.
if aerr, ok := err.(*authentication.Error); ok {
    switch aerr.Err {
    case "authorization_pending":
        // Continue polling
        continue
    case "slow_down":
        // Increase polling interval
        ticker.Reset(time.Duration(interval+5) * time.Second)
        continue
    case "expired_token":
        // Start a new CIBA flow
        log.Fatal("Authentication request expired")
    case "access_denied":
        // User denied the request
        log.Fatal("User denied authentication")
    }
}

Best Practices

  1. Respect Polling Intervals: Always wait at least the specified interval between polling requests.
  2. Handle Timeouts: Implement proper timeout handling based on the expires_in value.
  3. User Communication: Provide clear feedback to the user about the authentication request status.
  4. Error Handling: Implement robust error handling for all possible error states.
  5. Security: Always validate tokens received from the CIBA flow before trusting them.

See Also

Build docs developers (and LLMs) love