Skip to content

Authentication

FluxiQ PIX uses two authentication mechanisms: JWT tokens for internal API access and mTLS (mutual TLS) with ICP-Brasil certificates for BACEN communication.

API Authentication (JWT)

All internal API endpoints require a Bearer token in the Authorization header.

Login

POST /api/v1/auth/login

Request Body:

json
{
  "email": "admin@institution.com.br",
  "password": "your-password"
}

Response: 200 OK

json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "usr-123",
    "email": "admin@institution.com.br",
    "name": "Admin User",
    "roles": ["admin", "operator"]
  },
  "expires_in": 3600
}

Using the Token

Include the JWT token in all subsequent requests:

bash
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  http://localhost:4003/api/v1/keys

Token Refresh

Refresh an expired access token using the refresh token:

POST /api/v1/auth/refresh

Request Body:

json
{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response: 200 OK

json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600
}

Token Lifecycle

TokenLifetimeStorage
Access token1 hourMemory (frontend state)
Refresh token7 daysHttpOnly cookie or secure storage

JWT Claims

The access token contains the following claims:

json
{
  "sub": "usr-123",
  "email": "admin@institution.com.br",
  "roles": ["admin", "operator"],
  "ispb": "12345678",
  "iat": 1705312800,
  "exp": 1705316400,
  "iss": "fluxiq-pix"
}

Role-Based Access Control

RoleDescriptionPermissions
adminFull system accessAll operations
operatorDay-to-day operationsPayments, keys, monitoring
complianceCompliance and auditInfractions, reports, audit logs
viewerRead-only accessView dashboards and reports
api_clientProgrammatic accessAPI operations only

BACEN mTLS Authentication

Communication with BACEN DICT and SPI APIs uses mutual TLS (mTLS) with ICP-Brasil digital certificates. Both the client and server authenticate each other using X.509 certificates.

How mTLS Works

┌──────────────┐                          ┌──────────────┐
│  FluxiQ PIX  │                          │    BACEN     │
│   (Client)   │                          │   (Server)   │
└──────┬───────┘                          └──────┬───────┘
       │                                         │
       │  1. ClientHello                         │
       │────────────────────────────────────────>│
       │                                         │
       │  2. ServerHello + Server Certificate    │
       │<────────────────────────────────────────│
       │                                         │
       │  3. Client validates server cert        │
       │     (BACEN CA chain)                    │
       │                                         │
       │  4. Client Certificate                  │
       │────────────────────────────────────────>│
       │                                         │
       │  5. Server validates client cert        │
       │     (ICP-Brasil CA chain)               │
       │                                         │
       │  6. Encrypted channel established       │
       │<══════════════════════════════════════>│
       │                                         │

Certificate Types

ICP-Brasil A1 Certificate

  • Software-stored certificate (PKCS#12 / PFX format)
  • Valid for 1 year
  • Private key stored in file system
  • Suitable for server-to-server communication

ICP-Brasil A3 Certificate

  • Hardware-stored certificate (HSM or smart card)
  • Valid for up to 5 years
  • Private key never leaves the hardware device
  • Higher security, required for production by some institutions

Certificate Setup

1. Obtain Your Certificate

Request an ICP-Brasil e-CNPJ certificate from an accredited Certificate Authority (AC):

  • Certisign
  • Serasa Experian
  • Valid Certificadora
  • Soluti

The certificate must contain your institution's CNPJ and the ISPB registered with BACEN.

2. Extract PEM Files

If you received a PFX/PKCS#12 file, extract the PEM components:

bash
# Extract the client certificate
openssl pkcs12 -in certificate.pfx -clcerts -nokeys -out client.pem

# Extract the private key
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out client_key.pem

# Extract the CA chain
openssl pkcs12 -in certificate.pfx -cacerts -nokeys -chain -out ca_chain.pem

# Set appropriate permissions
chmod 600 client_key.pem
chmod 644 client.pem ca_chain.pem

3. Install BACEN Root CA

Download BACEN's root CA certificate for server verification:

bash
# Download BACEN CA (check BACEN's official documentation for the current URL)
curl -o bacen_ca.pem https://www.bcb.gov.br/content/estabilidadefinanceira/pix/bacen-ca.pem

4. Configure the Application

elixir
# config/runtime.exs
config :shared, Shared.Bacen.Client,
  # Client certificate and key
  cert_path: System.get_env("CERT_PATH"),
  key_path: System.get_env("KEY_PATH"),

  # CA chain for server verification
  ca_path: System.get_env("CA_PATH"),
  bacen_ca_path: System.get_env("BACEN_CA_PATH"),

  # TLS options
  tls_versions: [:"tlsv1.2", :"tlsv1.3"],

  # Connection settings
  pool_size: 50,
  max_overflow: 25,
  timeout: 10_000

5. Verify the Connection

bash
# Test mTLS connection to BACEN homologation
openssl s_client \
  -connect dict-h.pi.rsfn.net.br:16522 \
  -cert client.pem \
  -key client_key.pem \
  -CAfile ca_chain.pem \
  -verify 4 \
  -tls1_2

Implementation Details

The Shared BACEN client handles mTLS at the Erlang :ssl level:

elixir
defmodule Shared.Bacen.Client do
  @moduledoc "mTLS-authenticated HTTP client for BACEN APIs"

  def request(method, path, body \\ nil, opts \\ []) do
    url = base_url() <> path

    headers = [
      {"Content-Type", "application/json"},
      {"Accept", "application/json"},
      {"PI-RequestingParticipant", ispb()},
      {"PI-PaymentServiceProvider", ispb()}
    ]

    ssl_opts = [
      certfile: String.to_charlist(config(:cert_path)),
      keyfile: String.to_charlist(config(:key_path)),
      cacertfile: String.to_charlist(config(:ca_path)),
      verify: :verify_peer,
      depth: 4,
      customize_hostname_check: [
        match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
      ],
      versions: [:"tlsv1.2", :"tlsv1.3"]
    ]

    Finch.build(method, url, headers, encode_body(body))
    |> Finch.request(Shared.Finch, receive_timeout: 10_000, pool_timeout: 5_000)
    |> handle_response()
  end
end

JWS Message Signing

In addition to mTLS transport security, SPI messages require JWS (JSON Web Signature) signing:

elixir
defmodule Shared.Bacen.JwsSigner do
  @moduledoc "Signs messages with JWS for BACEN SPI"

  def sign(payload, opts \\ []) do
    private_key = load_private_key(config(:key_path))

    header = %{
      "alg" => "PS256",
      "typ" => "JWT",
      "kid" => key_id(private_key)
    }

    {_, signed} = JOSE.JWS.sign(private_key, Jason.encode!(payload), header)
    signed
  end

  def verify(jws_token) do
    public_key = load_bacen_public_key()
    {verified, payload, _} = JOSE.JWS.verify(public_key, jws_token)
    if verified, do: {:ok, Jason.decode!(payload)}, else: {:error, :invalid_signature}
  end
end

XML Digital Signatures

ISO 20022 XML messages sent to SPI require XML Digital Signatures (XMLDSig):

elixir
defmodule Shared.Bacen.XmlSigner do
  def sign_xml(xml_document) do
    private_key = load_private_key(config(:key_path))
    certificate = load_certificate(config(:cert_path))

    xml_document
    |> add_signature_element(private_key, certificate)
    |> canonicalize()
    |> compute_digest()
    |> sign_with_key(private_key)
  end
end

Security Best Practices

Certificate Management

  • Store private keys with restricted file permissions (chmod 600)
  • Never commit certificates to version control
  • Use environment variables or secrets management for certificate paths
  • Monitor certificate expiration and renew 30 days before expiry
  • Keep backup copies of certificates in a secure vault

Network Security

  • All BACEN communication occurs over the RSFN network
  • Enable network-level firewalling to restrict outbound connections
  • Use a dedicated network interface for BACEN traffic
  • Log all mTLS handshake failures for security monitoring

Key Rotation

elixir
# Implement certificate rotation without downtime
defmodule Shared.Bacen.CertRotation do
  def rotate(new_cert_path, new_key_path) do
    # Verify new certificate is valid
    {:ok, _} = verify_certificate(new_cert_path, new_key_path)

    # Update configuration atomically
    Application.put_env(:shared, :cert_path, new_cert_path)
    Application.put_env(:shared, :key_path, new_key_path)

    # Restart connection pool to use new certificates
    Supervisor.restart_child(Shared.Supervisor, Shared.Bacen.Pool)

    :ok
  end
end

Audit Trail

All authentication events are logged for compliance:

  • Login attempts (success and failure)
  • Token issuance and refresh
  • mTLS handshake results
  • Certificate validation outcomes
  • Permission denied events

FluxiQ PIX - Plataforma Brasileira de Pagamento Instantaneo