Trabaja con alcances de OAuth2

Esta página se aplica a Apigee y Apigee Hybrid.

Consulta la documentación de Apigee Edge.

En este tema, se analiza cómo usar y aplicar permisos de OAuth 2.0 con Apigee.

¿Qué es un permiso de OAuth2?

Los permisos de OAuth 2.0 proporcionan una forma de limitar la cantidad de acceso que se otorga a un token de acceso. Por ejemplo, un token de acceso emitido para una app cliente puede recibir acceso de LECTURA y ESCRITURA a recursos protegidos o solo acceso de LECTURA. Puedes implementar tus API para aplicar cualquier permiso o combinación de permisos que desees. Por lo tanto, si un cliente recibe un token con permiso de LECTURA, y trata de llamar a un extremo de API que requiere acceso de ESCRITURA, la llamada fallará.

En este tema, analizaremos cómo se asignan los permisos a los tokens de acceso y cómo Apigee aplica los permisos de OAuth 2.0. Después de leer este tema, podrás usar permisos de confianza.

¿Cómo se asignan los permisos a los tokens de acceso?

Cuando Apigee genera un token de acceso, puede asignar un permiso para ese token. Para comprender cómo sucede esto, primero debes conocer las siguientes entidades de Apigee: productos de API, desarrolladores y apps de desarrollador. Para ver una introducción, consulta Introducción a la publicación. Te recomendamos que revises este material si necesitas continuar.

Un token de acceso es una string larga de caracteres de aspecto aleatorio que permite que Apigee verifique las solicitudes entrantes de la API (considéralo como una fuente para credenciales de nombre de usuario y contraseña típicas). Técnicamente, el token es una clave que hace referencia a una colección de metadatos que se ve de la siguiente manera:

{
  "issued_at" : "1416962591727",
  "application_name" : "0d3e1d41-a59f-4d74-957e-d4e3275d4781",
  "scope" : "A",
  "status" : "approved",
  "api_product_list" : "[scopecheck1-bs0cSuqS9y]",
  "expires_in" : "1799", //--in seconds
  "developer.email" : "scopecheck1-AdBmANhsag@apigee.com",
  "organization_id" : "0",
  "token_type" : "BearerToken",
  "client_id" : "eTtB7w5lvk3DnOZNGReBlvGvIAeAywun",
  "access_token" : "ODm47ris5AlEty8TDc1itwYPe5MW",
  "organization_name" : "wwitman",
  "refresh_token_expires_in" : "0", //--in seconds
  "refresh_count" : "0"
}

Los metadatos del token incluyen la string del token de acceso, la información de vencimiento, la identificación de la app para desarrolladores, el desarrollador y los productos asociados con el token. También notarás que los metadatos incluyen "permiso".

¿Cómo obtiene su permiso el token?

La primera clave para comprender el permiso es recordar que cada producto en una app de desarrollador puede tener cero o más permisos asignados. Estos permisos se pueden asignar cuando se crea el producto o se pueden agregar más adelante. Existen como una lista de nombres y se incluyen en los "metadatos" asociados con cada producto.

Cuando creas una app de desarrollador y le agregas productos, Apigee analiza todos los productos en la app para desarrolladores y crea una lista de todos los permisos de esos productos (la lista de permisos principal o global de la app: una unión de todos los permisos reconocidos).

Cuando una app cliente solicita un token de acceso de Apigee, puede especificar de forma opcional qué permisos desea asociar con ese token. Por ejemplo, la siguiente solicitud solicita el permiso “A”. Es decir, el cliente solicita que el servidor de autorización (Apigee) genere un token de acceso que tenga el permiso “A” (dándole a la aplicación la autorización para llamar a las API que tienen el permiso “A”). La aplicación envía una solicitud POST como la siguiente:

curl -i -X POST -H Authorization: Basic Mg12YTk2UkEIyIBCrtro1QpIG \
  -H content-type:application/x-www-form-urlencoded \
  https://apitest.acme.com/oauth/token?grant_type=client_credentials&scope=A

¿Qué ocurre entonces?

Cuando Apigee recibe esta solicitud, sabe qué app realiza la solicitud y sabe qué app para desarrollador se registró (el ID de cliente y las claves secretas del cliente están codificadas en el encabezado de autenticación básico). Debido a que se incluye el parámetro de búsqueda scope, Apigee debe decidir si alguno de los productos de API asociados con la app para desarrolladores tiene el permiso “A”. Si los tiene, se genera un token de acceso con el permiso “A”. Otra forma de ver esto es que el parámetro de consulta de permisos es un tipo de filtro. Si la app para desarrolladores reconoce los permisos “A, B, X”, y el parámetro de consulta especifica “scope=X Y Z”, entonces solo se asignará el permiso “X” al token.

¿Qué sucede si el cliente no adjunta un parámetro de permiso? En este caso, Apigee genera un token que incluye todos los permisos reconocidos por la app para desarrolladores. Es importante comprender que el comportamiento predeterminado es mostrar un token de acceso que contenga la unión de todos los permisos de todos los productos incluidos en la app para desarrolladores.

Si ninguno de los productos asociados a una app para desarrolladores especifica permisos y un token tiene un permiso, las llamadas realizadas con ese token fallarán.

Supongamos que una app para desarrolladores reconoce estos permisos: A B C D. Esta es la lista principal de permisos de la app. Puede ser que un producto en la app tenga los permisos A y B, y otro tenga los permisos C y D, o cualquier combinación. Si el cliente no especifica un parámetro de scope (o si especifica un parámetro de permiso sin valor), se le otorgará el token a los cuatro permisos: A, B, C y D. Nuevamente, el token recibe un conjunto de permisos que es la unión de todos los permisos que reconoce la aplicación para desarrolladores.

Existe otro caso en el que el comportamiento predeterminado es mostrar un token de acceso con todos los permisos reconocidos, que es cuando la política GenerateAccessToken (la política de Apigee que genera tokens de acceso)no me encuentro en especifica un elemento <Scope>. Por ejemplo, esta es una política de GenerateAccessToken en la que se especifica <Scope>. Si falta ese elemento <Scope> (o si está presente, pero vacío), se ejecuta el comportamiento predeterminado.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-GenerateAccessToken">
    <DisplayName>OAuthV2 - Generate Access Token</DisplayName>
    <Attributes>
      <Attribute name='hello' ref='system.time' display='false'>value1</Attribute>
    </Attributes>
    <Scope>request.queryparam.scope</Scope> 
    <GrantType>request.formparam.grant_type</GrantType>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>GenerateAccessToken</Operation>
    <SupportedGrantTypes>
      <GrantType>client_credentials</GrantType>
    </SupportedGrantTypes>
  <GenerateResponse enabled="true"/>
</OAuthV2>

¿Cómo se aplican los permisos?

Primero, recuerda que en Apigee, los tokens de acceso se validan con la política OAuthV2 (por lo general, se ubica al principio de un flujo de proxy). La política debe tener especificada la operación VerifyAccessToken. Veamos esta política:

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenA">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A</Scope> <!-- Optional: space-separated list of scope names. -->
    <GenerateResponse enabled="true"/>
</OAuthV2>

Observa el elemento <Scope>. Se usa para especificar qué permisos acepta la política.

En este ejemplo, la política tendrá éxito solo si el token de acceso incluye el permiso "A". Si se omite este elemento <Scope> o si el elemento no tiene valor, la política ignora el permiso del token de acceso.

Ahora, con la capacidad de validar tokens de acceso basados en los permisos, puedes diseñar tus API para aplicar permisos específicos. Para ello, diseña flujos personalizados con políticas VerifyAccessToken adaptadas a los permisos y asociadas a esos flujos.

Supongamos que tu API tiene un flujo definido para el extremo /resourceA:

<Flow name="resourceA">
            <Condition>(proxy.pathsuffix MatchesPath "/resourceA") and (request.verb = "GET")</Condition>
            <Description>Get a resource A</Description>
            <Request>
                <Step>
                    <Name>OAuthV2-VerifyAccessTokenA</Name>
                </Step>
            </Request>
            <Response>
                <Step>
                    <Name>AssignMessage-CreateResponse</Name>
                </Step>
            </Response>
        </Flow>

Cuando se activa este flujo (ingresa una solicitud con /resourceA en el sufijo de la ruta), se llama de inmediato a la política OAuthV2-VerifyAccessTokenA. Esta política verifica que el token de acceso sea válido y verifica qué permisos admite el token. Si se configura la política como en el ejemplo que aparece a continuación, con <Scope>A</Scope>, la política solo tendrá éxito si el token de acceso tiene el permiso “A”. De lo contrario, se mostrará un error.

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenA">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A</Scope>
    <GenerateResponse enabled="true"/>
</OAuthV2>

En resumen, los desarrolladores de API son responsables de diseñar la aplicación de permisos en sus API. Para ello, crean flujos personalizados a fin de controlar permisos específicos y vinculan políticas VerifyAccessToken para aplicar esos permisos.

Ejemplos de código

Finalmente, veamos algunas llamadas a la API de ejemplo para ayudar a ilustrar cómo los tokens reciben permisos y cómo se aplican los permisos.

Caso predeterminado

Supongamos que tienes una app de desarrollador con productos y que la unión de los permisos de esos productos es A, B y C. Esta llamada a la API solicita un token de acceso, pero no especifica un parámetro de consulta de permiso.

curl -X POST -H content-type:application/x-www-form-urlencoded \
  https://apitest.acme.com/scopecheck1/token?grant_type=client_credentials

En este caso, el token generado recibirá los permisos A, B y C (el comportamiento predeterminado). Los metadatos del token se verán de la siguiente manera:

{
  "issued_at" : "1417016208588",
  "application_name" : "eb1a0333-5775-4116-9eb2-c36075ddc360",
  "scope" : "A B C",
  "status" : "approved",
  "api_product_list" : "[scopecheck1-yEgQbQqjRR]",
  "expires_in" : "1799", //--in seconds
  "developer.email" : "scopecheck1-yxiuHuZcDW@apigee.com",
  "organization_id" : "0",
  "token_type" : "BearerToken",
  "client_id" : "atGFvl3jgA0pJd05rXKHeNAC69naDmpW",
  "access_token" : "MveXpj4UYXol38thNoJYIa8fBGlI",
  "organization_name" : "wwitman",
  "refresh_token_expires_in" : "0", //--in seconds
  "refresh_count" : "0"
}

Ahora, supongamos que tienes un extremo de API con permiso “A” (es decir, es VerifyAccessToken requiere el permiso “A”). Esta es la política de VerifyAccessToken:

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenA">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A</Scope>
    <GenerateResponse enabled="true"/>
</OAuthV2>

Esta es una llamada de muestra a un extremo que aplica el permiso A:

curl -X GET -H Authorization: Bearer MveXpj4UYXol38thNoJYIa8fBGlI \
  https://apitest.acme.com/scopecheck1/resourceA

Esta llamada GET se realiza correctamente:

 {
   "hello" : "Tue, 25 Nov 2014 01:35:53 UTC"
 }

La operación se ejecuta de forma correcta porque la política VerifyAccessToken que se activa cuando se llama al extremo requiere el permiso A, y al token de acceso se le otorgaron los permisos A, B y C (el comportamiento predeterminado).

Caso de filtrado

Supongamos que tienes una app para desarrolladores con productos que tienen los permisos A, B, C y X. Solicita un token de acceso y, además, incluye el parámetro de búsqueda scope, de la siguiente manera:

curl -i -X POST -H content-type:application/x-www-form-urlencoded \
  'https://apitest.acme.com/oauth/token?grant_type=client_credentials&scope=A X'

En este caso, al token generado se le asignarán los permisos A y X, porque A y X son permisos válidos. Recuerda que la app para desarrolladores reconoce los permisos A, B, C y X. En este caso, filtras la lista de productos de API según estos permisos. Si un producto tiene un permiso A o X, puedes configurar los extremos de API que aplicarán estos permisos. Si un producto no tiene permiso A o X (supongamos que tiene B, C y Z), no se puede llamar a las API que aplican los permisos A o X con el token.

Cuando llamas a la API con el token nuevo, sucede lo siguiente:

curl -X GET -H Authorization: Bearer Rkmqo2UkEIyIBCrtro1QpIG \
  https://apitest.acme.com/scopecheck1/resourceX

El proxy de API valida el token de acceso. Por ejemplo:

<OAuthV2 async="false" continueOnError="false" enabled="true" name="OAuthV2-VerifyAccessTokenX">
    <DisplayName>Verify OAuth v2.0 Access Token</DisplayName>
    <ExternalAuthorization>false</ExternalAuthorization>
    <Operation>VerifyAccessToken</Operation>
    <Scope>A X</Scope>
    <GenerateResponse enabled="true"/>
</OAuthV2>

Los activadores de llamadas GET tienen éxito y se muestra una respuesta. Por ejemplo:

 {
   "hello" : "Tue, 25 Nov 2014 01:35:53 UTC"
 }
 

La operación se ejecuta con éxito porque la política VerifyAccessToken requiere los permisos A o X, y el token de acceso incluye los permisos A y X. Por supuesto, si el elemento <Scope> se hubiera configurado como “B”, esta llamada fallaría.

Resumen

Es importante comprender cómo Apigee maneja los permisos de OAuth 2.0. A continuación, se presentan los puntos principales:

  • Una app de desarrollador “reconoce” la unión de todos los permisos definidos para todos sus productos.
  • Cuando una app solicita un token de acceso, tiene la oportunidad de especificar qué permisos desea tener. Le corresponde a Apigee (el servidor de autorización) determinar los permisos que realmente asignará al token de acceso según (a) los permisos que se solicitan y (b) los que se reconoce la app de desarrollador.
  • Si Apigee no está configurado para verificar el permiso (falta el elemento <Scope> en la política VerifyAccessToken o si está vacío), la llamada a la API se realizará de forma correcta siempre que el permiso incorporado en el token de acceso. coincide con uno de los permisos que reconoce la app para desarrolladores registrada (uno de los permisos de la lista “principal” de permisos de la app).
  • Si un token de acceso no tiene ningún permiso asociado, solo tendrá éxito en casos en los que Apigee no tenga en cuenta el permiso (falta el elemento <Scope> en la política VerifyAccessToken o que está vacía).