openapi: 3.0.3
info:
  title: API Autenticación interna
  description: |
    <div class="apidoc-overview-rich">
    <div class="intro">
    <span class="eyebrow">Autenticación interna · OAuth 2.0</span>
    <h1>API de Autenticación interna</h1>
    <p class="lead">El <strong>API de Autenticación interna</strong> emite tokens JWT para aplicaciones y servicios del equipo Coordinadora mediante <em>OAuth 2.0 Client Credentials</em> sobre el realm corporativo de Keycloak (<code>prod-auth</code>). Usa estos tokens en las APIs marcadas como uso interno.</p>
    </div>
    <div class="flow-section">
    <h2 class="flow-label">Flujo OAuth 2.0 · Client Credentials (interno)</h2>
    <div class="flow-diagram">
    <div class="flow-actors" role="img" aria-label="Diagrama del flujo OAuth 2.0 interno">
    <div class="flow-actor flow-actor--app">
    <i class="ti ti-device-laptop" aria-hidden="true"></i>
    <span class="flow-actor-name">Tu App</span>
    <span class="flow-actor-sub">Servicio interno</span>
    </div>
    <div class="flow-step flow-step--request">
    <span class="flow-step-label">1 · client_id + client_secret</span>
    <span class="flow-step-arrow" aria-hidden="true">→</span>
    </div>
    <div class="flow-actor flow-actor--keycloak">
    <i class="ti ti-shield-check" aria-hidden="true"></i>
    <span class="flow-actor-name">Keycloak</span>
    <span class="flow-actor-sub">POST …/openid-connect/token</span>
    </div>
    <div class="flow-step flow-step--issue">
    <span class="flow-step-label">2 · JWT (expires_in)</span>
    <span class="flow-step-arrow" aria-hidden="true">→</span>
    </div>
    <div class="flow-actor flow-actor--token">
    <i class="ti ti-ticket" aria-hidden="true"></i>
    <span class="flow-actor-name">JWT Token</span>
    <span class="flow-actor-sub">Authorization: Bearer</span>
    </div>
    </div>
    </div>
    </div>
    <div class="two-col">
    <div class="info-card">
    <div class="info-card-head">
    <i class="ti ti-help-circle" aria-hidden="true"></i>
    <h3>¿Cuándo usarlo?</h3>
    </div>
    <div class="info-card-body">
    <div class="info-row">
    <i class="ti ti-point-filled" aria-hidden="true"></i>
    <span>Al integrar un microservicio o batch interno con APIs de Coordinadora.</span>
    </div>
    <div class="info-row">
    <i class="ti ti-point-filled" aria-hidden="true"></i>
    <span>Antes de cada llamada cuando el token haya expirado.</span>
    </div>
    </div>
    </div>
    <div class="info-card">
    <div class="info-card-head">
    <i class="ti ti-checklist" aria-hidden="true"></i>
    <h3>Requisitos previos</h3>
    </div>
    <div class="info-card-body">
    <div class="info-row">
    <div class="step-badge">1</div>
    <span><code>client_id</code> y <code>client_secret</code> registrados en Keycloak interno.</span>
    </div>
    <div class="info-row">
    <div class="step-badge">2</div>
    <span>Acceso de red al host de Keycloak corporativo.</span>
    </div>
    </div>
    </div>
    </div>
    <div class="code-block">
    <div class="code-top">
    <div class="code-label">
    <span class="m-post">POST</span>
    /protocol/openid-connect/token
    </div>
    </div>
    <div class="code-pre"><span class="cc"># Request (application/x-www-form-urlencoded)</span>
    <span class="ck">grant_type</span>=<span class="cs">client_credentials</span>
    <span class="ck">client_id</span>=<span class="cs">tu-client-id</span>
    <span class="ck">client_secret</span>=<span class="cs">tu-client-secret</span>
    <span class="cc"># Response</span>
    {
    <span class="ck">"access_token"</span>: <span class="cs">"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."</span>,
    <span class="ck">"token_type"</span>:   <span class="cs">"Bearer"</span>,
    <span class="ck">"expires_in"</span>:   3600
    }</div>
    </div>
    <div class="bp-grid">
    <div class="bp-card do">
    <i class="ti ti-circle-check" aria-hidden="true"></i>
    <p><strong>Cachea el token</strong> en memoria hasta poco antes de expirar.</p>
    </div>
    <div class="bp-card dont">
    <i class="ti ti-circle-x" aria-hidden="true"></i>
    <p><strong>No compartas</strong> el <code>client_secret</code> fuera de secret managers internos.</p>
    </div>
    </div>
    <div class="spec-note">
    <i class="ti ti-file-code" aria-hidden="true"></i>
    <span>Los esquemas, respuestas y la consola <strong>Try it out</strong> están en las secciones siguientes de este documento.</span>
    </div>
    </div>
  version: 1.0.0

servers:
  - url: https://cmautenticacion-dev.coordinadora.com/realms/dev-auth
    description: Keycloak interno (entorno dev)

tags:
  - name: Token
    description: Emisión de tokens OAuth2

paths:
  /protocol/openid-connect/token:
    post:
      tags: [Token]
      summary: Obtener access token (client_credentials)
      description: Emite un access token JWT contra el realm corporativo de Keycloak usando OAuth 2.0 client_credentials.
      operationId: obtenerToken
      parameters:
        - name: Accept-Encoding
          in: header
          required: false
          schema:
            type: string
          example: identity
          description: Indica cómo se debe codificar la respuesta.
        - name: Accept
          in: header
          required: false
          schema:
            type: string
          example: application/json
          description: Define el formato de la respuesta esperada.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [grant_type, client_id, client_secret]
              properties:
                grant_type:
                  type: string
                  enum: [client_credentials]
                  example: client_credentials
                  description: Tipo de flujo OAuth2 utilizado. Debe ser `client_credentials`.
                scope:
                  type: string
                  example: openid
                  description: Alcance de los permisos del token (opcional, depende del servidor).
                client_id:
                  type: string
                  example: tu-client-id
                  description: Identificador del cliente registrado en el servidor de autenticación.
                client_secret:
                  type: string
                  format: password
                  example: tu-client-secret
                  description: Clave secreta del cliente, utilizada para la autenticación.
            examples:
              default:
                summary: Solicitud típica
                value:
                  grant_type: client_credentials
                  client_id: tu-client-id
                  client_secret: tu-client-secret
                  scope: openid
      responses:
        '200':
          description: Token emitido correctamente
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '400':
          description: Solicitud inválida (datos faltantes o credenciales mal formadas)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingCredentials:
                  summary: Credenciales faltantes
                  value:
                    error: invalid_request
                    error_description: Missing client_id or client_secret
        '401':
          description: Credenciales inválidas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  schemas:
    TokenResponse:
      type: object
      properties:
        access_token:       { type: string }
        expires_in:         { type: integer, example: 300 }
        refresh_expires_in: { type: integer }
        token_type:         { type: string, example: Bearer }
        not-before-policy:  { type: integer }
        scope:              { type: string }
    ErrorResponse:
      type: object
      properties:
        error:             { type: string, example: invalid_client }
        error_description: { type: string }
