Hackaton API Days Mediterránea

El día previo a los APIDays Mediterránea tuvo lugar un Hackatón donde se pretendía fomentar el uso y distribución de APIs web. Se crearon diversas categorías y nos presentamos en dos: desarrollo de APIs y uso de Twilio.

20 minutos para concebir una API

Durante la tarde del hackatón tuvimos que ponernos las pilas para idear algo útil y de valor que construir sobre la API de Twilio y que pudiese ser ofrecido, a su vez, como una API. No conocíamos Twilio y había que asumir que parte de la tarde tendríamos que pasarla leyendo la documentación y realizando pruebas con su API. Así que decidimos realizar una API sencilla que permitiese a los clientes verificar la autenticidad de un teléfono, al igual que se hace habitualmente con los correos electrónicos. Lo llamamos Veriphone, eso sí, antes de saber que la marca ya estaba registrada.

El valor de Veriphone

La API que queríamos desarrollar tenias dos propuestas de valor complementarias. Por un lado, debía permitir descartar spammers en el registro de usuarios de un sitio web al igual que lo hace un captcha, es decir, interactuando con el usuario a través del teléfono para descartar que se trate de un bot. Por otro, debía permitir asegurar ciertas acciones en una aplicación o sitio web pidiendo al usuario información que solo él pudiera conocer, y a través de un dispositivo que solo él tuviera.

Si un buen sistema de seguridad se basa en tres patas: quién eres, qué sabes y qué posees; Veriphone debía aprovechar dos de estas. Lo que el usuario sabe, una clave temporal que debe comunicar por teléfono. Lo que el usuario posee, el teléfono que actúa como llave. A continuación, os contamos cómo lo hicimos.

¿Cómo funciona?

1. El consumidor de la API solicita la verificación de una operación que está realizando un determinado usuario.

a. Para ello envía a la API el número de teléfono del usuario.

b. Como respuesta se le envía un código PIN aleatorio para que se lo muestre al usuario.

2. Seguidamente se realiza una llamada telefónica al usuario en cuestión y se le solicita que introduzca el PIN que se le ha mostrado previamente.

3. El usuario introduce el PIN a través del teclado de su móvil.

4. Una vez que el usuario introduce el PIN en su teléfono se invoca una URL de callback en el consumidor de la API para informar del resultado de la verificación.

Arquitectura

arquitectura

Diseño

Modelo de Entidades

Diseño

  • Account

Cada Account se corresponde con un cliente de la API (consumidor). Se compone de:

    • Un nombre
    • Un dominio desde el que se harán las llamadas a la API
    • A cada consumidor se le asignará una API Key que sirve para identificarle
    • Cada consumidor tendrá una lista de teléfonos sobre los que podrá solicitar verificaciones
publicclassAccount{
privateStringname;
privateStringdomain;
privateStringapiKey;
privateList<Phone>phones;
}
  • Phone

Teléfono asociado a un cliente de la API sobre el que se podrán hacer verificaciones. Se compone de:

    • Un número de teléfono
    • Una lista de verificaciones que se han realizado sobre ese teléfono.
publicclassPhone{
privateStringnumber;
privateAccountaccount;
privateList<Verification>verifications;
}
  • Verification

Verificación asociada a un teléfono. Se compone de:

    • Un estado, que puede tomar los valores: PENDING, FAILED o VERIFIED
    • Una fecha de solicitud de la verificación, que permite comprobar si la verificación se realiza en un determinado periodo de tiempo
    • Un código PIN aleatorio
    • Un teléfono sobre el que se hace la verificación
    • Una URL de callback del cliente de la API a la que se invoca para comunicar el resultado de la verificación
    • El identificador de la llamada de Twilio que se ha realizado para hacer la verificación
publicclassVerification{
privateLongid;
privateVerificationStatusstatus;
privateStringcode;
privateDaterequestDate;
privateStringcallSid;
privateStringcallbackUrl;
privatePhonephone;
}
  • Comentarios sobre el diseño de entidades

La URL de callback se ha establecido a nivel de Verification y no a nivel de Account para permitir que una misma aplicación web tenga varios puntos de verificación con distintas URL de callback.

Endpoints

  • Registrar cliente en el API

post account

Permite registrar un cliente de la API y obtener una API Key para poder realizar llamadas al mismo.

@RequestMapping(value="/accounts",method=RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
publicResponseEntity<String>createCustomer(@RequestBodyAccountaccount)throws
JsonGenerationException,JsonMappingException,IOException{
AccountcreatedAccount=customerService.createAccount(account);
SimpleFilterProviderfilters=newSimpleFilterProvider();
filters.addFilter("accountFilter",SimpleBeanPropertyFilter.serializeAllExcept("id","phones"));
Stringjson=objectMapper.writer(filters).writeValueAsString(createdAccount);
returnnewResponseEntity<String>(json,HttpStatus.CREATED);}
  • Asociar un teléfono a un cliente

post phonesPermite asociar a un cliente de la API los números de teléfono que se quieren verificar. La relación entre Phoney Account se hará a través de la API Key recibida en la cabecera HTTP Authorization.

@RequestMapping(value="/phones",method=RequestMethod.POST,consumes=MediaType.APPLICATION_JSON_VALUE,
produces=MediaType.APPLICATION_JSON_VALUE)
publicResponseEntity<String>createPhone(@RequestBodyPhonephone,@RequestHeader("Authorization")
Stringauthorization)throwsJsonGenerationException,JsonMappingException,IOException,
AccountNotFoundException{
PhonecreatedPhone=phoneService.createPhone(AuthorizationHeaderParser.getApiKey(authorization),
phone);
SimpleFilterProviderfilters=newSimpleFilterProvider();
filters.addFilter("phoneFilter",SimpleBeanPropertyFilter.serializeAllExcept("account",
"verifications"));Stringjson=objectMapper.writer(filters).writeValueAsString(createdPhone);
returnnewResponseEntity<String>(json,HttpStatus.CREATED);
}
  • Crear una verificación para un teléfono

verificationPermite crear una verificación para un número de teléfono en concreto. Devuelve la verificación en estado PENDING con un código PIN aleatorio que se deberá mostrar al usuario. También se encarga de realizar la llamada al usuario para solicitarle el PIN.

@RequestMapping(method=RequestMethod.POST,value="/phones/{phoneNumber}/verifications",
consumes=MediaType.APPLICATION_JSON_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
publicResponseEntity<String>createVerification(@PathVariable("phoneNumber")StringphoneNumber,
@RequestBodyVerificationverification,@RequestHeader("Authorization")Stringauthorization)throws
TwilioRestException,JsonGenerationException,JsonMappingException,IOException,
AccountNotFoundException{
VerificationcreatedVerification=
verificationService.createVerification(AuthorizationHeaderParser.getApiKey(authorization),phoneNumber,
verification);
SimpleFilterProviderfilters=newSimpleFilterProvider();
filters.addFilter("verificationFilter",SimpleBeanPropertyFilter.serializeAllExcept("id","phone"));
Stringjson=objectMapper.writer(filters).writeValueAsString(createdVerification);
returnnewResponseEntity<String>(json,HttpStatus.CREATED);
}
  • Comprobar el estado de una verificación

callsid

Permite consultar el estado de una verificación

@RequestMapping(method=RequestMethod.GET,value="/verifications/{callSid}",
produces=MediaType.APPLICATION_XML_VALUE)
publicResponseEntity<String>getVerification(@PathVariable("callSid")StringcallSid,
@RequestHeader("Authorization")Stringauthorization)throwsJsonGenerationException,
JsonMappingException,IOException,AccountNotFoundException,VerificationNotFoundException{
Verificationverification=
verificationService.getVerification(AuthorizationHeaderParser.getApiKey(authorization),callSid);SimpleFilterProviderfilters=newSimpleFilterProvider();
filters.addFilter("verificationFilter",SimpleBeanPropertyFilter.serializeAllExcept("id","phone"));
Stringjson=objectMapper.writer(filters).writeValueAsString(verification);
returnnewResponseEntity<String>(json,HttpStatus.OK);
}
  • Recoger el código tecleado en el teléfono

digits

Este es un endpoint especial que se invoca desde Twilio para recoger el código que ha tecleado el usuario en el teclado de su teléfono móvil, compararlo con el código registrado, actualizar el estado de la verificación e invocar a la URL de callback para comunicar cual ha sido el resultado de la verificación.

@RequestMapping(method=RequestMethod.GET,params={"CallSid","Digits"},
produces=MediaType.APPLICATION_XML_VALUE)
publicResponseEntity<String>checkVerification(@RequestParam("CallSid")StringcallSid,
@RequestParam("Digits")Stringdigits)throwsVerificationNotFoundException{
verificationService.checkVerification(callSid,digits);
returnnewResponseEntity<String>(twilioService.getTwilioAfterGatherMessage(),HttpStatus.OK);
}

La respuesta de este endpoint es un xml con instrucciones para Twilio. Un ejemplo de respuesta sería la siguiente:

<Response>
<Sayvoice="woman">Wehavereceivedsuccessfullyyourpinnumber.</Say>
</Response>

En este caso le damos a Twilio el mensaje de la locución telefónica para indicarle al usuario que hemos recibido el PIN que ha introducido.

  • Obtener instrucciones para llamada de teléfono

twiml call

Este también es un endpoint especial que se invoca desde Twilio cuando se hace la llamada de teléfono para recoger cuáles son las instrucciones que tiene que seguir.

@RequestMapping(method={RequestMethod.GET,RequestMethod.POST},value="/twiml-call",produces=MediaType.APPLICATION_XML_VALUE)
publicResponseEntity<String>getTwilioCallMessage(){
returnnewResponseEntity<String>(twilioService.getTwilioCallMessage(),HttpStatus.OK);
}

Este es un ejemplo de respuesta:

<Response>
<Gathertimeout="20"finishOnKey="*"method="GET"
action="http://veriphone.byteflair.cloudbees.net/api/v1/verifications">
<Sayvoice="woman">Pleaseenteryourpinnumberandthenpressstar.</Say>
</Gather>
</Response>

Lo que se le dice a Twilio en esta respuesta es que tiene que solicitarle al usuario que introduzca una serie de números a través del teclado del teléfono, y que el ‘*’ servirá para indicar el final. El usuario dispondrá de 20 segundos para introducirlos. Además se le indica a Twilio una URL a la que se invocará cuando el usuario introduzca el caracter de fin o cuando expiren los 20 segundos:

<Gathertimeout="20"finishOnKey="*"method="GET"
 action="http://veriphone.byteflair.cloudbees.net/api/v1/verifications">

También se le indica a Twilio cuál es el mensaje de la locución telefónica donde se le explicará al usuario lo que tiene que hacer:

<Sayvoice="woman">Pleaseenteryourpinnumberandthenpressstar.</Say>

Seguridad

Como mecanismo de seguridad se utiliza una API Key. Todo cliente de la API tendrá que registrarse indicando un dominio desde el que hará las llamadas y se le asignará una clave (API Key). Esta clave tendrá que ser enviada como cabecera HTTP en todas las llamadas que se hagan a la API.

En cada petición la API comprobará que está recibiendo la cabecera con la clave y que el dominio desde el que se está haciendo esa petición se corresponde con el de esa clave.

La API dispone de un frontend para registrarse como cliente de la API y obtener la clave de uso del mismo. Este frontend será el único que tendrá acceso al endpoint de registro de clientes de la API (POST/accounts).

Autores: Daniel Cerecedo y Diego Castro (Ganadores de la Hackatón de APIdays 
Mediterránea en la categoría de Creación de una API)
Web: http://byteflair.com/
Tagged with →  
Share →