La autorización es un componente importante a la hora de trabajar con aplicaciones  serverless, esto se ha convertido en un reto ya que entre servicios intercambian información que puede contener datos sensibles, y que muchas veces por temas de certificaciones implican aplicar procesos para asegurar quien puede ver o modificar esta información, una de las principales diferencias en un flujo de autorización usado una SPA (utilzando OAuth2)  con un flujo de autorización en microservicios, es básicamente la interacción humana, como se puede ver en la Figura 1

Figura 1: Flujo de autorización con Oauth2 obtenido de https://docs.oracle.com/cd/E50612_01/doc.11122/oauth_guide/content/oauth_intro.html

Este tipo de flujo de autorización es actualmente muy usado, lo pueden apreciar cuando ustedes entran en alguna pagina y presionan "Login with" como se muestra en la Figura 2, este flujo viene de la utilización del framework Oauth2 en donde básicamente busca que la autorización se realice por medio de terceros, lo que le quita esa complejidad a la aplicación y recae sobre servicios como Facebook, Google, Twitter etc, este flujo pertenece a aplicaciones web en donde existe la intervención humana

Figura 2: Flujo de autorización con intervención de usuario Obtenido de https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc

Cognito

Es un servicio de AWS que permite el inicio de sesión y registro utilizando mas que nada identity social media, es decir  autorizadores muy famosos como Gmail, Facebook e Instagram, en donde el factor diferenciador es que la integración es muy sencilla, para el flujo de client credentials, que es el flujo que en este caso necesitamos en los microservicios, se puede apreciar en la Figura 3, es el flujo machine-to-machine cognito nos permite implementar este flujo utilizando como validador de authorizacion su servicio, para ello veamos un ejemplo en código

Figura 3: Flujo de client credentials obtenida de https://is.docs.wso2.com/en/latest/learn/client-credentials-grant/

Lo primero que se debe hacer en cognito es crear un user pool, cognito tiene tambien algo llamado identy pool pero este servicio se usa mas es la autorizacion de roles dentro del mismo AWS, entonces dentro del user pool, creare una aplicación de la siguiente forma

Figura 4: Creando una aplicación en cognito

y lo configuro permitiendo client creentials y a los recursos que tendrá acceso como se ve a continuación, en mi caso creo un recursos y un scope custom

Figura 5: Configurando una aplicación en cognito

ahora en apps settings tenemos la información para generar el token, con ese domain lo que haremos en según su documentacion , realizar el flujo para obtener el token

Figura 6: Información de un user pool en cognito

para generar el token hacemos un llamado a la api de generar token de la siguiente forma

curl --request POST \
  --url https://user-tet.auth.us-east-1.amazoncognito.com/oauth2/token \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'Authorization: Basic NXIzcmE2cW00MzZyYWlwcnBtaDdibjg5bjA6OGwxcXBhazRpNG0xcWp0OTU1N2NpOW92bnMzcW5rMGV2a2Y5Z2Vlb21vODRpbmZxM2xp' \
  --data "grant_type=client_credentials"

según la doc, en Basic debe ir client_id:client_secret en base64 y esto genera el siguiente token, el cual es un jwt

{"access_token":"eyJraWQiOiJlREd6ZEM3alwvZzNuVGdYbkVnUG1XWkJ5NGF4T01yOEY0bm9jXC9UMTZBSWs9IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiI1cjNyYTZxbTQzNnJhaXBycG1oN2JuODluMCIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoidXNlci10ZXRcL2NyZWF0ZV9lbmRwb2ludCIsImF1dGhfdGltZSI6MTY0Mjg3ODg5NiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfWU14eEhYazc0IiwiZXhwIjoxNjQyODgyNDk2LCJpYXQiOjE2NDI4Nzg4OTYsInZlcnNpb24iOjIsImp0aSI6IjU3NzQyMDY1LTExZTgtNDllOC1hOGM4LTE0Nzk2MzUyMWU1OCIsImNsaWVudF9pZCI6IjVyM3JhNnFtNDM2cmFpcHJwbWg3Ym44OW4wIn0.0INIrSA5C-9eq2uuZjO3n-stXQYUvfxFURpaMPTSULpT3JPd1sKivywruZVXMUIU3Az1YtTARRd7QYJ-CE3KgD8vfnr18nnieG00lxw4rFfNk7dPU0_3zY9hI-nyl2-RxoCUKUwxx3vB-Onj-1ujmsuLrxiDES2jteZU7ocID1veOTyShFlidOwBFEnvFrxTFo2hqKpu_faOzUQU6SS44SYpVsEqPyNYUFNtFR3i6IdSbfep1Ojd526F8kUvhoT7tNRMGHseobHl_d3aIogsIunfv12JkuuNBLprCaFHd2a69Fvtrt_v9M_Z8kANTBcuqbao45c6ArljrGl7J8D1xA","expires_in":3600,"token_type":"Bearer"}

la codificación del token la realiza automáticamente serverless, ahora veremos como realizar la configuración usando serverless, para ello genere una nueva aplicación en python  y agregue el siguiente código al yaml


service: mycognito-cognito-auth
frameworkVersion: '2'
variablesResolutionMode: 20210326
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  httpApi:
    authorizers:
      MyProjectAuthorizer:
        type: jwt
        identitySource: $request.header.Authorization
        issuerUrl:  ${file(./config.${opt:stage, 'dev'}.json):ISSUER_URL}
        audience:
          -  ${file(./config.${opt:stage, 'dev'}.json):AUDIENCE}

functions:
  hello:
    handler: handler.hello
    events:
      - httpApi:
          method: POST
          path: /upload
          authorizer:
            name: MyProjectAuthorizer

y el código del handler en python quedaría asi

def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

despliego el código con sls deploy y lo probamos usando la API, primero sin enviar el token

Figura 7: Petición a la API con token erróneo

y ahora enviando el token correcto generado anteriormente

Figura 9: Petición a la API con token valido

algo importante en la respuesta del encabezado es el atributo requestContext, que lo podríamos revisar mejor si lo sacamos aparte

  "requestContext": {
      "accountId": "290390632196",
      "apiId": "8wmdwfhqih",
      "authorizer": {
        "jwt": {
          "claims": {
            "auth_time": "1642878896",
            "client_id": "5r3ra6qm436raiprpmh7bn89n0",
            "exp": "1642882496",
            "iat": "1642878896",
            "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_YMxxHXk74",
            "jti": "57742065-11e8-49e8-a8c8-147963521e58",
            "scope": "user-tet/create_endpoint",
            "sub": "5r3ra6qm436raiprpmh7bn89n0",
            "token_use": "access",
            "version": "2"
          },
          "scopes": null
        }
      }
      }

serverless automáticamente decodifica el jwt y deja la información en el header, claramente después de realizar la validación de que el token esta vigente y es valido utilizando el issuerurl, ademas valida que el atributo audience concuerde con el audience generado anteriormente, esto nos genera un problema por que en mi contexto de negocio necesito tener varias aplicaciones con la misma audiencia, esto debido a que dependido de lo que se desea hace pueden existir diferentes enfoques, todos totalmente validos, sin embargo en mi caso si quisiera autenticar otra app es decir otro servicio, tendría que tener una configuración como la siguiente

service: mycognito-cognito-auth
frameworkVersion: '2'
variablesResolutionMode: 20210326
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  httpApi:
    authorizers:
      MyProjectAuthorizer:
        type: jwt
        identitySource: $request.header.Authorization
        issuerUrl:  ${file(./config.${opt:stage, 'dev'}.json):ISSUER_URL}
        audience:
          -  ${file(./config.${opt:stage, 'dev'}.json):AUDIENCE}
          -  ${file(./config.${opt:stage, 'dev'}.json):AUDIENCE_TWO}
          -  ${file(./config.${opt:stage, 'dev'}.json):AUDIENCE_THREE}
          -  ${file(./config.${opt:stage, 'dev'}.json):AUDIENCE_N}

functions:
  hello:
    handler: handler.hello
    events:
      - httpApi:
          method: POST
          path: /upload
          authorizer:
            name: MyProjectAuthorizer

quizás exista una mejor forma de hacerlo con cognito, pero no esta claro en la documentación y por lo tanto esto implicaría mas configuraciones adicionales, por esto para el contexto de mi problemática surge como opción Auth0

Auth0

Es basciamente un servicio enfocado a prestar y garantizar la authenticacion y authorizacion de manera eficiente, segura y configurable, es desde el punto de vista cloud authorizacion as a service que ademas (como rigor y no publicidad paga) fue ubicada en 2021 como un servicio líder en el cuadrante de gartner, con todo se se puede apreciar que Auth0 es un servicio con años de experiencia en esta área, por lo tanto se han encontrado con miles de casos de uso que en su plataforma han estandarizado

Figura 10: Auth0 se posiciona en el cuadrante de innovadores Obtenido de https://auth0.com/blog/okta-and-auth0-recognized-as-a-leader-in-the-2021-gartner-magic-quadrant-for-access-management/

uno de esos casos es el machine-to-machine el cual es en el que se enfoca este articulo, para ello me dirigiré a la plataforma y creare una aplicación

Figura 11: Creación de aplicación en Auth0

Y luego configuraremos la API autorizada, que sera nuestra audiencia la cual podremos usar en varias aplicaciones

Figura 12: Configuración de aplicación en Auth0

Ahora generamos el token con el siguiente comando

curl --request POST \
  --url https://dev-ebjtqc6h.us.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{"client_id":"JRLDVIH3vq5R8spURthUXZxcgDuqoegu","client_secret":"LcIX4JzrlmArbuc0mwZU9elkd6qbmN78y0TMyviJbtmvMRnZ_J-IOOmbsgf6p08P","audience":"https://dev-ebjtqc6h.us.auth0.com/api/v2/","grant_type":"client_credentials"}'

y obtenemos el siguiente resultado

{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkxqaU1abkx0MzZnRWZsQ0l5SGRRcCJ9.eyJpc3MiOiJodHRwczovL2Rldi1lYmp0cWM2aC51cy5hdXRoMC5jb20vIiwic3ViIjoiSlJMRFZJSDN2cTVSOHNwVVJ0aFVYWnhjZ0R1cW9lZ3VAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZGV2LWVianRxYzZoLnVzLmF1dGgwLmNvbS9hcGkvdjIvIiwiaWF0IjoxNjQyODgwOTM2LCJleHAiOjE2NDI5NjczMzYsImF6cCI6IkpSTERWSUgzdnE1UjhzcFVSdGhVWFp4Y2dEdXFvZWd1Iiwic2NvcGUiOiJyZWFkOmNsaWVudF9ncmFudHMiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.NvzcyG7qfVLytYHYnS7SF4v6QuW32l-2lCRsF4RW1C6u9xGJHVHHWzyCRcVzdpQ2Yb9sgMSByQXHXA-RV3gVfBVpyep9sPWI_wD7WYMkWp-G-RRwTLasJJMygphTy_0D6ggkHxY8Ec5u1Lt42jcpScTZuaPyQUk8M-3oCBJ3aS9H4RsCgFjy8tbaanckINHSVcy5QX6WjFFy9diQJKBqpB3_PpqLrQcgvKgCPxQHpjDv7r4WiuVAJU3nEzz_yEQIHP3o_Du8QR1_zasrdUBPvcDosyoQC9JL4R05Rxam5f1Ztq5cbsey5doxSiVMGY_rqirlwmAAbKDfyXHglDV5mw","scope":"read:client_grants","expires_in":86400,"token_type":"Bearer"}

el serverless yaml no debemos cambiarlo, nos queda igual solamente debemos cambiar las variables que leemos del json

service: mycognito-cognito-auth
frameworkVersion: '2'
variablesResolutionMode: 20210326
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  httpApi:
    authorizers:
      MyProjectAuthorizer:
        type: jwt
        identitySource: $request.header.Authorization
        issuerUrl:  ${file(./config.${opt:stage, 'dev'}.json):ISSUER_URL}
        audience:
          -  ${file(./config.${opt:stage, 'dev'}.json):AUDIENCE}
         

functions:
  hello:
    handler: handler.hello
    events:
      - httpApi:
          method: POST
          path: /upload
          authorizer:
            name: MyProjectAuthorizer

y las variables quedaría de la siguiente forma

{
  "AUDIENCE": "https://dev-ebjtqc6h.us.auth0.com/api/v2/",
  "ISSUER_URL": "httpss://dev-ebjtqc6h.us.auth0.com/"
}

desplegamos y probamos de nuevo, dejando el token anterior que no deberia servir por que apunta al token de cognito

Figura 13: Petición a la API con token erróneo

Ahora actualizando el token veré lo siguiente

Figura 14: Petición a la API con token valido

y viendo la respuesta el requestContext

  "requestContext": {
      "accountId": "290390632196",
      "apiId": "8wmdwfhqih",
      "authorizer": {
        "jwt": {
          "claims": {
            "aud": "https://dev-ebjtqc6h.us.auth0.com/api/v2/",
            "azp": "JRLDVIH3vq5R8spURthUXZxcgDuqoegu",
            "exp": "1642967336",
            "gty": "client-credentials",
            "iat": "1642880936",
            "iss": "https://dev-ebjtqc6h.us.auth0.com/",
            "scope": "read:client_grants",
            "sub": "JRLDVIH3vq5R8spURthUXZxcgDuqoegu@clients"
          },
          "scopes": null
        }
      }
      }

el jwt igual se decodifica y se muestra su contenido, ahora a diferencia de cognito, en este caso si agrego un app client adicional no tendría que modificar el yaml, ya que auth0 permite agrear la misma audience a diferentes apps

En términos de quotas hay dos limites que realmente me parecen que a futuro pueden generar inconvenientes, por ejemplo en cognito tiene un limite de 1000 apps por user pool, cuando se llegue a este limite no se puede expandir y se debe crear otro pool lo cual conllevan un montón de configuraciones adicionales, otro limite es que el token puede tener una duración de máximo un día, entiendo que es por buenas practicas, pero como mencione al principio, para mi es muy importante que sea personalizable, en auth0 tenemos 100 apps como limite, pero se puede ampliar cambiando a un plan enterprise, y la duración máxima del token es de 30 días

Conclusiones

Los dos servicios cumplen con el framework OAuth2, sin embargo la implementacion de cognito es enfocada a la autorización con intervención de un usuario y auth0 se enfoca mucho en la personalización para la autorización de machine to machine lo cual es un valor de bastante peso para decidir entre los dos, por que a medida que se integra el servicio en tu aplicación requerirá diferentes personalizaciones