Este documento intenta apoyar al lector en el proceso de interactuar con el Identity Provider (IdP) de la plataforma de Recepción de Comprobantes Electrónicos del Ministerio de Hacienda. Le guiará por un paso a paso con información como ¿dónde se encuentra el IdP?, ¿cómo consumirlo?, ¿cuáles estándares se utilizan?. Le explicará con imágenes y ejemplos que le permitirán tener una mayor claridad del funcionamiento.

IMPORTANTE

Esta no es una guía oficial del Ministerio de Hacienda y de ninguna manera se relaciona directamente con ellos. Los puntos de vista y recomendaciones son nuestros únicamente, basados en la documentación y estándares indicados en la documentación de la plataforma de Recepción de Comprobantes Electrónicos.

Se le recomienda al lector leer primero la documentación de la plataforma antes de continuar con esta guía. La documentación oficial se encuentra en la página de login del ATV:

https://www.hacienda.go.cr/ATV/Login.aspx

La última versión al momento de escribir este documento es la 4.3, dar principal atención al Anexo #3 del documento de Anexos.

Documentación oficial MH

Documentación oficial MH

¿Qué es el IdP de Comprobantes Electrónicos?

Un IdP en inglés significa Identity Provider, este se encarga de los procesos de autenticación en la plataforma. Hay varios tipos de IdP y para varias funciones, por ejemplo el IdP se podría utilizar para proteger un sitio web, en este caso, al tratar de accesar el sitio web este redireccionaría al IdP para que le solicite los credenciales y luego el IdP lo envía de regreso al sitio web ya con una sesión creada.

El funcionamiento con el servicio REST API de Recepción de Comprobantes Electrónicos sería similar, para poder consumir el servicio se necesita previamente tener una sesión en el IdP. Esta sesión no sería con una pantalla de login, sino que es para interacción con REST APIs.

La plataforma trabaja utilizando un modelo de seguridad con OpenID Connect (OIDC) (http://openid.net/connect/), este es una capa de identidad arriba del estándar RFC6749 The OAuth 2.0 Authorization Framework (https://tools.ietf.org/html/rfc6749). El OIDC utiliza el estándar RFC7519 JSON Web Token (JWT) (https://tools.ietf.org/html/rfc7519), para almacenar la información de la sesión dentro del token del OAuth 2.0. El Grant Type utilizado por el IdP para autenticar usuarios es el Resource Owner Password Credential.

Ahora si, ¿qué quiere decir el párrafo anterior? Básicamente lo que nos interesa entender es que el modelo de seguridad utilizado en la plataforma es una extensión del OAuth 2.0, y que para la mayoría de los casos se comporta como un OAuth 2.0. El hecho de que sea OpenID Connect agrega funcionalidad interesante pero lo único que nos importa de este es usar la función de logout y utilizar los JWT dentro de los token del OAuth 2.0.

OAuth 2.0

El OAuth 2.0 tiene varios tipos de Grants y funcionalidades, como lo indica la documentación del Anexo #3, necesitamos utilizar el Resource Owner Password Credential Grant del Auth 2.0. En este es que debemos concentrarnos, sin embargo, si les recomiendo por cultura general leer el documento del estándar para que tengan una idea más completa, y porque toda la documentación no va a estar solamente en la sección de este grant, hay flujos y términos que se encuentran en otras secciones y se van complementando conforme se lee el estándar.

Resource Owner Password Credential Grant

El OAuth 2.0 tiene varios tipos de Grants, el que se debe utilizar es el Resource Owner Password Credential Grant.

La documentación del OAuth 2.0 indica que para el Resource Owner Password Credential Grant se deben utilizar unos credenciales, estos son los credenciales que se generan desde el sistema ATV del Ministerio de Hacienda, serían usuario y contraseña. Hay dos ambientes, producción y sandbox, los credenciales son diferentes para cada ambiente y se deben generar para cada uno.

Utilizando la documentación del OAuth 2.0 y el Anexo #3 de la documentación de la plataforma, se obtiene que para obtener un token se debe enviar un payload de tipo application/x-www-form-urlencoded que contenga los campos:

  • grant_type
  • client_id
  • username
  • password

Este debe responder con un payload en application/json;charset=UTF-8 donde se indicará al menos los atributos:

  • access_token
  • refresh_token

Estos dos token son realmente un JSON Web Token, esto es parte de lo que se extiende al utilizar el OpenID Connect.

En una sección más adelante ya se mostrará con ejemplos reales como quedaría este payload y la respuesta que se obtiene.

JSON Web Token

JSON Web Token es un estándar abierto (RFC 7519) que define una forma segura de transmitir información como un JSON Object. La información puede ser validada y confiable porque esta firmada digitalmente, el IdP se encarga de firmarlos.

Una ventaja muy importante de utilizar JWT es que este contiene toda la información requerida del usuario, evitando que sea necesario consultar un repositorio de datos cada vez.

En este caso para la plataforma, el JWT se utiliza principalmente de forma interna, pero es importante entender como funciona en caso de querer obtener alguna información del JWT como el tiempo de expiración del token, que igualmente se puede obtener al momento de generarlo el token en la respuesta del OAuth 2.0.

1
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6T29WS3AyNkhSY0lOQS1pOUhNWlpsYXlkbVJQVE5ROE9wd2hwb1BRTTNVIn0.eyJqdGkiOiI3ZWIxM2ExNC0zNmI1LTRiZmEtYjgxMC1kNmQwNGE4YTQ5MzUiLCJleHAiOjE1OTgyNTIxMTUsIm5iZiI6MCwiaWF0IjoxNTk4MjUxODE1LCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNTY2YjRkMTUtZjczMS00NWNkLWE0NWYtZGFlODk0YTgzMDBhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJiYWJjNDRlZC02Mjk1LTQ5M2ItOWNkMy0zMWFkNDYxMDFmZDYiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiIiLCJuYW1lIjoiTUFSVklOIFZJTklDSU8gTU9OR0UgQ0hBVkVTIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY3BmLTAxLTEzMDItMDQ4N0Bwcm9kLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jciIsInBvbCI6IjViODBiOWI5OWVjZTM2MDMyYjA3ZDJlZCIsImdpdmVuX25hbWUiOiJNQVJWSU4gVklOSUNJTyIsImZhbWlseV9uYW1lIjoiTU9OR0UgQ0hBVkVTIiwicG9saWN5LWlkIjoiNThhNjIwYTM3NmVhZTE0MDhjZTVlN2RmIiwiZW1haWwiOiJjcGYtMDEtMTMwMi0wNDg3QHByb2QuY29tcHJvYmFudGVzZWxlY3Ryb25pY29zLmdvLmNyIn0.eVozDMVNdbyW4ycbX10vfSAjlHoIpixeTwa8OznlsTmZ1H8al-hhOeTH4x-nt6EvjAK4riuQKZYB7DJ5gGa4hmrN3jegHWKeBTLjAaON3arlRc5YppdLJze83K5SxxLKDIRLWroCQmaw45D3NlIjQ57N90IHICoafMBJTtFqZ7DFsSliQqNjQpm2D9how5PvP6nls6vGaJ8mf20D1D8LuPjM_epJAl7AB8PZ0dvvTXVAEGIM9KvqX7zKIqE6AjIUL3tmgn-QKh4bfqTXV4Qq55ItPjD-p3-F6vERz-N3R7gqVSUeQ1KSlQvp4lPuZtIZIREpMmUmPfsZ64KKNB6esQ

Para ver la información del JWT se puede utilizar el decoder del sitio web https://jwt.io/ .

JWT Decoder

JWT Decoder

Interacción con el IdP

A continuación se muestra la información necesaria para interactuar con los dos ambientes (Realms) que expone el IdP de la plataforma, estos dos ambientes son:

  • Producción
  • Sandbox / Staging
Datos Producción Sandbox
TOKEN_URL https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/token https://idp.comprobanteselectronicos.go.cr/auth/realms/rut-stag/protocol/openid-connect/token
LOGOUT_URL https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/logout https://idp.comprobanteselectronicos.go.cr/auth/realms/rut-stag/protocol/openid-connect/logout
client_id api-prod api-stag
username Obtener de ATV Obtener de ATV
password Obtener de ATV Obtener de ATV

INFO

Los body o payload a enviar para interactuar con el IdP son de tipo x-www-form-urlencoded, y el IdP normalmente va a responder con application/json.

En la siguiente sección se explicará como utilizar el token que se obtuvo, esta sección se enfoca principalmente en la interacción con el IdP para mayor claridad.

Para interactuar con el IdP se van a utilizar principalmente 3 procesos:

  • Obtener un token
  • Refrescar un token
  • Cerrar sesión

TIP

Los pasos a continuación se van a explicar utilizando el IdP con el Realm de Producción. El password en los ejemplos no es el real como tampoco el usuario, por favor no ejecutar los comandos ya que les darán error, estos son principalmente con fines ilustrativos y para que el lector los pueda modificar para sus necesidades.

CUIDADO

Los password que da el ATV son cadenas de caracteres de aproximadamente un largo de 20, y pueden tener caracteres especiales. El body o payload que se envía al IdP es de tipo x-www-form-urlencoded, esto quiere decir que se debe revisar se envíen bien codificados los form fields, los caracteres especiales del password si se envían en PLAIN podrían dar errores indicando que los credenciales son inválidos.

Para cada proceso se mostrará la petición HTTP con cURL, HTTPie y el PLAIN HTTP, poner atención a como se ve el password en el PLAIN HTTP para tener claro el tema del x-www-form-urlencoded. Y se mostrará el output recibido únicamente del HTTPie para que sea más visual.

¿Cómo obtener un token?

Para obtener un token se debe enviar una petición HTTP POST al TOKEN_URL con los siguientes form fields:

Form Field Data
grant_type password
client_id api-prod
username Obtenido de ATV
password Obtenido de ATV

Algunas librerías o plataformas podrían solicitar el scope o el client_secret, en este caso se dejan en blanco, no son necesarias.

cURL, obtener token:

1
2
3
4
5
6
curl -X "POST" "https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/token" \
     -H 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \
     --data-urlencode "client_id=api-prod" \
     --data-urlencode "[email protected]" \
     --data-urlencode "password=HD.~q:-_6\$?cC6Nb*z)g" \
     --data-urlencode "grant_type=password"

HTTPie, obtener token:

1
2
3
4
5
6
http --form POST 'https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/token' \
    'Content-Type':'application/x-www-form-urlencoded; charset=utf-8' \
    'client_id'='api-prod' \
    'username'='[email protected]' \
    'password'='HD.~q:-_6$?cC6Nb*z)g' \
    'grant_type'='password'

PLAIN HTTP, obtener token:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
POST /auth/realms/rut/protocol/openid-connect/token HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 142
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: idp.comprobanteselectronicos.go.cr
User-Agent: HTTPie/2.2.0

client_id=api-prod&username=cpf-01-1234-5678%40prod.comprobanteselectronicos.go.cr&password=HD.~q%3A-_6%24%3FcC6Nb%2Az%29g&grant_type=password

Y se obtiene la siguiente respuesta:

HTTPie, respuesta de obtener token:

HTTPie, respuesta de obtener token

En la respuesta obtenida nos interesa principalmente los siguientes atributos:

Form Field Data
token_type Indica el tipo de token que se obtuvo en la petición, normalmente va a ser bearer, este es el que se necesita para trabajar con la plataforma.
access_token Este es el JWT que se va a utilizar para interactuar con la plataforma, cada vez que se haga una petición al REST API de Comprobantes Electrónicos se debe enviar este.
expires_in Tiempo de expiración en segundos del access_token, cuando el access_token expira se debe hacer un proceso de Refresh Token para obtener otro.
refresh_token Este es el token utilizado para el proceso de Refresh, este token tiene un tiempo de expiración mucho mayor que el access_token.
refresh_expires_in Tiempo de expiración en segundos del refresh_token, cuando el refresh_token expira se acabó la sesión y es necesario crear una nueva sesión.

NOTA

Al momento de actualizar esta guía (agosto 2020), el tiempo de expiración del access_token es de 5 minutos y el refresh_token es de 25 minutos. Se le recuerda no quemar estos valores ya que pueden variar sin previo aviso.

JWT Signature Algoritm (diferencia)

Hay una diferencia con el algoritmo de firma del JWT en el refresh_token, anteriormente ambos JWT utilizaban RS256 pero a partir de algún momento, el refresh_token cambió el algoritmo de firma a HS256. Para los que validaban estos JWT, como en mi caso, esto provocó un error ya que la validación de un HS256 solo la puede hacer Hacienda por lo que no podemos validar el refresh_token.

Para obtener más información del OpenID Connect del Realm de producción se puede utilizar estos URLs:

Well-known Open ID Configuration:

https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/.well-known/openid-configuration

JWKS Certs:

https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/certs

En caso de hacer la validación del JWT con RS256, se recomienda utilizar dinámicamente el URL de JWKS con un caché ya que Hacienda podría cambiar estos certificados en cualquier momento sin previo aviso.

¿Cómo refrescar un token?

Para refrescar un token se debe enviar una petición HTTP POST al TOKEN_URL con los siguientes form fields:

Form Field Data
grant_type refresh_token
client_id api-prod
refresh_token Data del refresh_token obtenido en el proceso anterior.

cURL, refrescar un token:

1
2
3
4
5
curl -X "POST" "https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/token" \
     -H 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \
     --data-urlencode "client_id=api-prod" \
     --data-urlencode "refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkY2NmYTVlYi02YjkzLTQ1MmQtOWM3MC0yYmI0NmI1MWI2NTMifQ.eyJqdGkiOiI4N2JlYzBkNy02NmU5LTQ1YjgtODc5MC1jMTBlOTRiNTc2MmEiLCJleHAiOjE1OTgyNTY4OTcsIm5iZiI6MCwiaWF0IjoxNTk4MjU1Mzk3LCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJzdWIiOiI1NjZiNGQxNS1mNzMxLTQ1Y2QtYTQ1Zi1kYWU4OTRhODMwMGEiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMmEwZGZjZS0zNzIzLTQxMWQtODM5Mi05MTU1YzllNjI5NzkiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IiJ9.Gw7aR42jJPZFQow2KE1THDm6Lt7T_dHt3FbosZ3i9wM" \
     --data-urlencode "grant_type=refresh_token"

HTTPie, refrescar un token:

1
2
3
4
5
http --form POST 'https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/token' \
    'Content-Type':'application/x-www-form-urlencoded; charset=utf-8' \
    'client_id'='api-prod' \
    'refresh_token'='eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkY2NmYTVlYi02YjkzLTQ1MmQtOWM3MC0yYmI0NmI1MWI2NTMifQ.eyJqdGkiOiI4N2JlYzBkNy02NmU5LTQ1YjgtODc5MC1jMTBlOTRiNTc2MmEiLCJleHAiOjE1OTgyNTY4OTcsIm5iZiI6MCwiaWF0IjoxNTk4MjU1Mzk3LCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJzdWIiOiI1NjZiNGQxNS1mNzMxLTQ1Y2QtYTQ1Zi1kYWU4OTRhODMwMGEiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMmEwZGZjZS0zNzIzLTQxMWQtODM5Mi05MTU1YzllNjI5NzkiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IiJ9.Gw7aR42jJPZFQow2KE1THDm6Lt7T_dHt3FbosZ3i9wM' \
    'grant_type'='refresh_token'

PLAIN HTTP, refrescar un token:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
POST /auth/realms/rut/protocol/openid-connect/token HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 897
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: idp.comprobanteselectronicos.go.cr
User-Agent: HTTPie/2.2.0

client_id=api-prod&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkY2NmYTVlYi02YjkzLTQ1MmQtOWM3MC0yYmI0NmI1MWI2NTMifQ.eyJqdGkiOiI4N2JlYzBkNy02NmU5LTQ1YjgtODc5MC1jMTBlOTRiNTc2MmEiLCJleHAiOjE1OTgyNTY4OTcsIm5iZiI6MCwiaWF0IjoxNTk4MjU1Mzk3LCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJzdWIiOiI1NjZiNGQxNS1mNzMxLTQ1Y2QtYTQ1Zi1kYWU4OTRhODMwMGEiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMmEwZGZjZS0zNzIzLTQxMWQtODM5Mi05MTU1YzllNjI5NzkiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IiJ9.Gw7aR42jJPZFQow2KE1THDm6Lt7T_dHt3FbosZ3i9wM&grant_type=refresh_token

Y se obtiene la siguiente respuesta:

HTTPie, respuesta de refrescar token:

HTTPie, respuesta de refrescar token

La respuesta obtenida es prácticamente la misma del proceso anterior de obtener un token, acá lo que se hace es igualmente obtener un token pero por medio de un refresh_token. Nos interesa principalmente los siguientes atributos:

Form Field Data
token_type Indica el tipo de token que se obtuvo en la petición, normalmente va a ser bearer, este es el que se necesita para trabajar con la plataforma.
access_token Este es el JWT que se va a utilizar para interactuar con la plataforma, cada vez que se haga una petición al REST API de Comprobantes Electrónicos se debe enviar este.
expires_in Tiempo de expiración en segundos del access_token, cuando el access_token expira se debe hacer un proceso de Refresh Token para obtener otro.
refresh_token Este es el token utilizado para el proceso de Refresh, este token tiene un tiempo de expiración mucho mayor que el access_token.
refresh_expires_in Tiempo de expiración en segundos del refresh_token, cuando el refresh_token expira se acabó la sesión y es necesario crear una nueva sesión.

¿Cómo cerrar sesión? (Eliminar el token)

Para cerrar sesión se debe enviar una petición HTTP POST al LOGOUT_URL con los siguientes form fields:

Form Field Data
client_id api-prod
refresh_token Data del refresh_token.

cURL, cerrar sesión:

1
2
3
4
curl -X "POST" "https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/logout" \
     -H 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \
     --data-urlencode "client_id=api-prod" \
     --data-urlencode "refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkY2NmYTVlYi02YjkzLTQ1MmQtOWM3MC0yYmI0NmI1MWI2NTMifQ.eyJqdGkiOiJlYzdhNjNmNy0xZDhlLTRmZDUtYTU1Yy00OWMxMjdkMWU1M2MiLCJleHAiOjE1OTgyNTgwMTUsIm5iZiI6MCwiaWF0IjoxNTk4MjU2NTE1LCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJzdWIiOiI1NjZiNGQxNS1mNzMxLTQ1Y2QtYTQ1Zi1kYWU4OTRhODMwMGEiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMmEwZGZjZS0zNzIzLTQxMWQtODM5Mi05MTU1YzllNjI5NzkiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IiJ9.0ydJ8-Ckc28ygGlbvJc73058wW7UvdkxDyAav8LEweA"

HTTPie, cerrar sesión:

1
2
3
4
http --form POST 'https://idp.comprobanteselectronicos.go.cr/auth/realms/rut/protocol/openid-connect/logout' \
    'Content-Type':'application/x-www-form-urlencoded; charset=utf-8' \
    'client_id'='api-prod' \
    'refresh_token'='eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkY2NmYTVlYi02YjkzLTQ1MmQtOWM3MC0yYmI0NmI1MWI2NTMifQ.eyJqdGkiOiJlYzdhNjNmNy0xZDhlLTRmZDUtYTU1Yy00OWMxMjdkMWU1M2MiLCJleHAiOjE1OTgyNTgwMTUsIm5iZiI6MCwiaWF0IjoxNTk4MjU2NTE1LCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJzdWIiOiI1NjZiNGQxNS1mNzMxLTQ1Y2QtYTQ1Zi1kYWU4OTRhODMwMGEiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMmEwZGZjZS0zNzIzLTQxMWQtODM5Mi05MTU1YzllNjI5NzkiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IiJ9.0ydJ8-Ckc28ygGlbvJc73058wW7UvdkxDyAav8LEweA'

PLAIN HTTP, cerrar sesión:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
POST /auth/realms/rut/protocol/openid-connect/logout HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 872
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: idp.comprobanteselectronicos.go.cr
User-Agent: HTTPie/2.2.0

client_id=api-prod&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkY2NmYTVlYi02YjkzLTQ1MmQtOWM3MC0yYmI0NmI1MWI2NTMifQ.eyJqdGkiOiJlYzdhNjNmNy0xZDhlLTRmZDUtYTU1Yy00OWMxMjdkMWU1M2MiLCJleHAiOjE1OTgyNTgwMTUsIm5iZiI6MCwiaWF0IjoxNTk4MjU2NTE1LCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJzdWIiOiI1NjZiNGQxNS1mNzMxLTQ1Y2QtYTQ1Zi1kYWU4OTRhODMwMGEiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMmEwZGZjZS0zNzIzLTQxMWQtODM5Mi05MTU1YzllNjI5NzkiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6IiJ9.0ydJ8-Ckc28ygGlbvJc73058wW7UvdkxDyAav8LEweA

Y se obtiene la siguiente respuesta:

HTTPie, respuesta de cerrar sesión:

HTTPie, respuesta de cerrar sesión

Importante notar de esta respuesta que se obtiene un HTTP 204 No Content, esta es la respuesta correcta al cerrar sesión.

Comunicación con la plataforma de Comprobantes Electrónicos

En las secciones anteriores se explicó un poco ¿qué es?, ¿cómo funciona? y ¿cómo interactuar? con el IdP, en esta sección se pretente explicar al lector ¿cómo poner en práctica todo para comunicarse con la plataforma?.

Ya luego de haber entendido las secciones anteriores, esta es muy simple. Para interactuar con el REST API de Recepción de Comprobantes Electrónicos es necesario enviar un header en cada HTTP request, este header es el Authorization header. El contenido del Authorization header se obtiene concatenando el bearer_type + [un espacio] + access_token. Por ejemplo:

1
Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6T29WS3AyNkhSY0lOQS1pOUhNWlpsYXlkbVJQVE5ROE9wd2hwb1BRTTNVIn0.eyJqdGkiOiI5N2M5NDcyYy05MDU5LTQ1ZWYtOTYyNC1jODdiMmQ2ZjUxMmEiLCJleHAiOjE1OTgyNTM5NjIsIm5iZiI6MCwiaWF0IjoxNTk4MjUzNjYyLCJpc3MiOiJodHRwOi8vaWRwLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jci9hdXRoL3JlYWxtcy9ydXQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNTY2YjRkMTUtZjczMS00NWNkLWE0NWYtZGFlODk0YTgzMDBhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBpLXByb2QiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJlODg2NDdhMS1jMDg3LTQ2ZmUtOGQzZi1hNDlmNjI3MjdhN2MiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiIiLCJuYW1lIjoiTUFSVklOIFZJTklDSU8gTU9OR0UgQ0hBVkVTIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY3BmLTAxLTEzMDItMDQ4N0Bwcm9kLmNvbXByb2JhbnRlc2VsZWN0cm9uaWNvcy5nby5jciIsInBvbCI6IjViODBiOWI5OWVjZTM2MDMyYjA3ZDJlZCIsImdpdmVuX25hbWUiOiJNQVJWSU4gVklOSUNJTyIsImZhbWlseV9uYW1lIjoiTU9OR0UgQ0hBVkVTIiwicG9saWN5LWlkIjoiNThhNjIwYTM3NmVhZTE0MDhjZTVlN2RmIiwiZW1haWwiOiJjcGYtMDEtMTMwMi0wNDg3QHByb2QuY29tcHJvYmFudGVzZWxlY3Ryb25pY29zLmdvLmNyIn0.TmzRIgLmdVolrSFKMa20Ri984F-_49KSf8SO6Ocjtmi-ZJbM5uXzfn4ixqSBGbf7ryBTrBv5rvF2soi2vs6-6KNgyvphbX7i1ewVk129uQwnG1_wPYjAWv4dbdR6LOdH7tUdQutiOsky8eRmZtthCDgpLE228sQElZoJ-Z2q5r5UQfZJ1PhC2GFe_6tGO2emEvj2LTLzd1d2H7wgmqmagxa9LNEI2o2nlDZjPbZyx9fKYCMqClbKIRo0bqdpez3Ug8PfEb9scmQFwNWJnvu22GgmgvB6a4y1YLSGaIwK48aYJXvcY8ZG4WpYVV6vrtqgOJmE9sKm0hvpwxyDRPEnPQ

Lo importante acá que se debe tener en cuenta es siempre enviar un access_token que no haya expirado, es por esto que desde la aplicación cliente que va a interactuar con la plataforma se debe manejar el ciclo de vida de la sesión.

Ciclo de vida de la sesión

La sesión se crea la primera vez que se obtiene el access_token, tendrá un tiempo de vida definido por el expires_in, al momento de hacer este documento son 5 minutos. Esto quiere decir que se debe controlar este tiempo de vida del access_token para saber que antes de enviarlo, si ya expiró, es necesario refrescarlo. Esto se podría hacer de varias maneras, ejemplo se podría tener un proceso que cuando va a enviar a la plataforma revisa si tiene un access_token que no haya expirado, sino lo tiene entonces revisa que tenga un refresh_token no expirado (el refresh_expires_in al momento de hacer el documento son 25 minutos), para poder refrescar el token, y si tampoco lo tiene entonces crea una sesión nueva.

La mejor práctica es si ya terminó de usar una sesión que la cierre utilizando el proceso de cerrar sesión de la sección anterior. Esto sería similar a cuando se entra al portal bancario en un navegador web, la práctica segura es cerrar la sesión al terminar para evitar alguna actividad maliciosa.

TIP

Si usted no es un usuario final del REST API de Comprobantes Electrónicos, sino que está creando un sistema que va a interactuar con varios usuarios finales de forma concurrente, la forma más práctica de manejar el ciclo de vida de la sesión es con un tipo de caché o base de datos en memoria para almacenar estos JWT. Debe asegurarse de no dar JWT a usuarios que no son sus dueños.

Siempre debe evitar crear un alto número de sesiones para el mismo usuario en el IdP de Hacienda ya que esto podría provocar que lo contacten para que lo solucione, o que lo bloqueen si lo consideran necesario. Además que crear este alto número de sesiones en el IdP tiene un consumo innecesario de recursos y tiempo de espera en el sistema que está desarrollando.

Publicación original

La información de este artículo fue creada originalmente en AsciiDoc como un manual para imprimir, más adelante fue ajustada para publicarla en el blog de Flecha Roja Technologies S.A., empresa donde me encontraba laborando. Por esta razón quiero colocar a continuación el URL donde se encuentra publicado:

https://flecharoja.com/blog/2017-12/mh-guia-idp/

Algunos clientes me hacen mucho consultas sobre este tema, así que decidí sacar el tiempo de actualizar la guía y refrescar los ejemplos con pruebas realizadas en agosto del 2020. Además de tratar de seguir un poco más el formato original que tenía el AsciiDoc.