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 el Date 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:

Si bien todo está aún en camino de definición completa, visto que el 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=
La API-V3, actualmente soporta sea 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:

  1. HTTP-Method (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD)
  2. HTTP-Header Digest
  3. HTTP-Header Date
  4. Todos los HTTP-Header que empiezan con X-ACS- canonizados.
  5. 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
Como sabrás el \n es el new-line ya en varios lenguajes (ASCII code 10).

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 header Digest es obligatorio.
  • Algunos toolkits dificultan la asignación del HTTP-Header Date. En esos caso podrán usar el header X-ACS-Date. El valor del header Date, así como el de X-ACS-Date, debe ser en el formato definido por RFC 1123. Si existe el header X-ACS-Date, en la validación del mensaje, se ignora el valor del header Date. En el caso de no existir el header Date, se deberán incluir \n, en la posición previstas para el header Date, en la canonicalización del mensaje (ver cálculo HMAC y ejemplo 2).
  • El header Date y el X-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 sea
PUT /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.