Authentication requests V3
Authorization header
Cada request debe contener información de autenticación para establecer la identidad de la aplicación que lo realiza.
La información de autenticación del request es contenida en el HTTP-Header Authorization
.
Authorization : ACS-HMAC {AppKey}:{HMAC}
- ACS-HMAC es nuestro authentication scheme. Cualquier otro tipo de authentication scheme, por ahora, no será interpretado como válido.
- AppKey es la información que identifica tu app.
-
HMAC es el resultado del algoritmo que permitirá autenticar tu request. Cabe aclarar que el algoritmo hace que el HMAC cambia a cada requests.
Si en un muy corto plazo se repite tu HMAC los requests que sigarán el primero serán todos descartados enviandote un
HTTP-StatusCode 401 (Unauthorized)
. Es importante que entiendas este punto porque, en el curso de lo años, hemos visto programadores volviéndose locos sólo por este motivo (ojo a como asignas elDate
porque allí está el chiste).
Digest header
Como seguramente sabrás, hace años que el algoritmo de hashing MD5 está deprecado y por consecuencia se deprecó también nuestro querido header Content-MD5
.
Varias empresas usaron, como remplazo, su propio custom/vendor header peeeero, en estos últimos años, la IETF (Internet Engineering Task Force) enfrentó el tema haciendo una buenísima propuesta.
Al momento de escribir estas frases hay documentación disponible en:
- https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-digest-headers
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Digest
Digest
está sea para requests que para responses, y dado que el HTTP "es una manteca" (cómo decía el gran Maestro @ajlopez), nos sumamos.Como verás el
Digest
, en requests, cumple con el mismo objetivo del viejo Content-MD5
pero de forma mucho más flexible ya que soporta múltiples algoritmos de hashing incluso contemporáneamente.
Digest: sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
sha-256
que sha-512
.¿Como usarás el header
Digest
? De la misma manera que lo hacías con el Content-MD5
: en tu pipeline del request, luego de asignar el content y previo al cálculo del HMAC, calcularás y asignarás el Digest
escogiendo uno de los hashings soportados.
Cálculo HMAC
El HMAC se calcula sobre una representación canónica del request en un string. Las variables que concurren al cálculo son:
- HTTP-Method (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD)
- HTTP-Header
Digest
- HTTP-Header
Date
- Todos los HTTP-Header que empiezan con
X-ACS-
canonizados. - Encoded path+query del request
Canonización HTTP-Headers X-ACS
Los HTTP-Headers específicos de Autocosmos, cuyos nombres empiezan con X-ACS- son canonizados de la siguiente forma:
- Lower-case de nombre del HTTP-Header (ej: para
X-ACS-Date
se obtiene x-acs-date) - Valores de un HTTP-Header space-trimmed y separados por coma (compliant sección 4.2 de RFC 2616.)
- Ordenados por nombre del HTTP-Header lower-case (ej: x-acs-a x-acs-b)
- Concatenando de todas las tuplas de {header-name}:{header-value} separándolas por new-line
Teniendo el siguiente set de headers
X-ACS-V1 : Valor 1 X-ACS-UpdAndDown : otro valor X-ACS-A1 : multi , valor
Se obtiene la siguiente representación canónica
x-acs-a1:multi,valor\nx-acs-updanddown:otro valor\nx-acs-v1:Valor 1
Otras consideraciones para tener en cuenta para el cálculo del HMAC
- El string resultante de la representación canónica del mensaje debe ser UTF-8 encoded.
- Debido a la naturaleza de algunos requests, el header
Digest
es opcionál (en un HTTP-GET no hay content del request), a pesar de esto se deberá incluir\n
en las posición prevista en la canonización del mensaje (ver cálculo HMAC). - Si un request tiene
Content-Length
mayor a 0, el headerDigest
es obligatorio. -
Algunos toolkits dificultan la asignación del HTTP-Header
Date
. En esos caso podrán usar el headerX-ACS-Date
. El valor del headerDate
, así como el deX-ACS-Date
, debe ser en el formato definido por RFC 1123. Si existe el headerX-ACS-Date
, en la validación del mensaje, se ignora el valor del headerDate
. En el caso de no existir el headerDate
, se deberán incluir\n
, en la posición previstas para el headerDate
, en la canonicalización del mensaje (ver cálculo HMAC y ejemplo 2). - El header
Date
y elX-ACS-Date
, si presentes en el request, deberán tener un valor que no diste más de 5 minutos desde la hora del servidor de Autocosmos.
Resultado
Una vez hayas terminado los tests para obtener la representación canonica del request, lo único que te queda es calcular el HMAC para luego asignarlo en el HTTP-Header Authorization
.
Para completar el hash del request se usa HMAC-SHA256 (http://www.ietf.org/rfc/rfc4868.txt) con tu AppSecret y luego usar su representación base64-encoded-string para asignarlo a Authorization
.
No te asustes con esa documentación porque el algoritmo está públicamente disponible prácticamente en todos los lenguajes de programación medianamente modernos.
REST Authentication: Ejemplo 1
Suponiendo el HEAD de su request seaPUT /algo/5 HTTP/1.0 Digest: sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= Content-Type: application/json Date: Thu, 17 Nov 2013 18:49:58 GMT X-ACS-Magic: abracadabra
El string de representación canónica del mensaje es:
PUT\nsha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=\nThu, 17 Nov 2013 18:49:58 GMT\nx-acs-magic:abracadabra\n/algo/5
REST Authentication: Ejemplo 2
Suponiendo el HEAD de su request sea
GET /algo/5 HTTP/1.0 Date: XXXXXXXXX X-ACS-Date: Thu, 17 Nov 2013 18:49:58 GMT
El string de representación canónica del mensaje es:
GET\n\n\nx-acs-date:Thu, 17 Nov 2013 18:49:58 GMT\n/algo/5
En este caso un \n
está presente para los headers Digest
y Date
.
Cálculo HMAC en Javascript (Extracto de ejemplo interactivo de uso de API)
Generación de canónico
var api3CanonicalSignatureString = function (method, digest, momentstr, acsHeaders, pathQuery) { var getCanonicalParts = function () { /* 1.Request method (GET,POST,PUT,DELETE,PATCH,OPTIONS,HEAD) 2.Value header Digest 3.Value header Date (si disponible) 4.Todos los headers que empiezan con X-ACS- canonizados. 5.URI (path + query) del request */ var parts = []; parts.push(method); digest && digest.length > 0 ? parts.push(digest) : parts.push(''); momentstr && momentstr.length > 0 ? parts.push(momentstr) : parts.push(''); if (acsHeaders) { // Headers de ACS ordenados, lowercase, trimmed Array.from(acsHeaders) .sort(function (a, b) { return a[0].toLowerCase().trim() > b[0].trim(); }) .forEach(function (e, idx) { parts.push(e[0].toLowerCase().trim() + ':' + e[1]); }); } parts.push(pathQuery); return parts; }; // Todos los componentes de la signature son separados por new-line (\n) return getCanonicalParts().join('\n'); };
Generación de Authentication
var getAuthPwdToken = function (canonicalized) { // No hay diferencia entre API-v2 y API-v3 // NOTE : CryptoJS automagically converts strings to UTF-8 bytes ARRAY var appsecret = $appsecret.val(); var hmac = CryptoJS .HmacSHA256(canonicalized, appsecret) .toString(CryptoJS.enc.Base64); return hmac; };
Creación de los headers del request
var api3Headers = function (httpMethod, endpoint, payload, accept, contentType, appkey) { var usarStandardHeaderDate = false , digest = payload ? 'sha-256=' + CryptoJS.SHA256(payload).toString(CryptoJS.enc.Base64) : null , moment = (new Date()).toISOString() , acsHeaders = usarStandardHeaderDate ? new Map() : new Map([['X-ACS-Date', moment]]) , headers = {}; var canonicalized = api3CanonicalSignatureString(httpMethod, digest, usarStandardHeaderDate ? moment : null, acsHeaders, endpoint); var pwdToken = getAuthPwdToken(canonicalized); headers['Authorization'] = 'ACS-HMAC ' + appkey + ':' + pwdToken; headers['Accept'] = accept; usarStandardHeaderDate && (headers['Date'] = moment); digest !== null && (headers['Digest'] = digest); contentType && (headers['Content-Type'] = contentType); acsHeaders.forEach(function (v, k) { headers[k] = v; }); return headers; }
La variable usarStandardHeaderDate
está para que veas la diferencia entre usar y no usar el standard header Date
(es muy probable que, en el lenguaje que uses no sea necesario usar X-ACS-Date
)
Authentication de requests con POSTMAN
Otro ejemplo, siempre en JS, puedes verlo en la configuración para usar la API3 con Postman.
¿Cómo pruebo que, lo que hice para la Authentication, funciona?
Para probar que tu pipeline de request hace todo lo necesario para enviar cualquier request a nuestra API es suficiente que uses los endpoints de tests: Tests. Si logras enviar requests a todos los endpoints de test, sin recibir un solo 401, ya estarás en la condición de empezar a trabajar en serio ;) .
Te recomendamos que antes de desarrollar lo que necesites leas antentamente nuesta sección de Día a Día.