Artículo
· 15 hr atrás Lectura de 6 min

Persistencia de sesión Oauth con token OpenID en cookieContestant

¿Conoces a Google? Seguro que si 😄 a menudo hacemos login en webs con nuestra cuenta de Gmail por la comodidad de simplemente hacer click! sin tener que escribir email ni contraseña, esto es posible porque nuestro navegador guarda un token de acceso que nos identifica y, en este caso Google, comparte un acceso para poder consultar información de nosotros como el correo electrónico.

🔐 Existen unas pautas o proceso para hacer esta identificación de forma segura, lo que se conoce como Oauth.

En este artículo no voy a explicar cómo funciona Oauth, te mostraré cómo hacer para persistir la sesión en el Oauth de IRIS sin tener que introducir usuario y contraseña cada vez que entras y de paso cómo saltar la pantalla de aceptación de permisos.

Aquí tienes una imagen marcando el flujo que vamos a crear.

¡¡Vamos al lío!!

Como primer paso puedes montar todo un sistema de Oauth en IRIS con el proyecto https://github.com/intersystems-ib/workshop-iris-oauth2, en el archivo Readme tienes los pasos para hacerlo funcionar con Docker.

Abre desde VS code el área de trabajo del proyecto.

 

 

Crea una clase que extienda de %OAuth2.Server.Authenticate, en nuestro caso la hemos llamado cysnet.oauth.server.Authenticate.

 

 

Configura Oauth para usar la clase personalizada y añade las siguientes dos métodos.

 

Y aquí la joya de la corona, crea dos métodos en la clase personalizada.

ClassMethod LoginFromCookie(authorizationCode As %String) As %Status [ Internal, ServerOnly = 1 ]
{
    #dim sc As %Status = $$$OK
    Set currentNS = $NAMESPACE
    Try {
        // Get cookie with jwt access token
        Set cookieToken = %request.GetCookie("access_token")
        If cookieToken '= "" {
            ZNspace "%SYS"
            // Get valid access token from cookie
            Set accessToken = ##class(OAuth2.Server.AccessToken).OpenByToken(cookieToken,.sc)
            If $$$ISOK(sc) && $ISOBJECT(accessToken) {
                // Get current access token
                Set currentToken = ##class(OAuth2.Server.AccessToken).OpenByCode(authorizationCode,.sc)
                If $$$ISOK(sc) && $ISOBJECT(currentToken) {
                    // Get oauth client
                    Set client = ##class(OAuth2.Server.Client).Open(currentToken.ClientId,.sc)
                    If $$$ISOK(sc) && $ISOBJECT(client) {
                        // Skip login page
                        Set currentToken.Username = accessToken.Username
                        #dim propertiesNew As %OAuth2.Server.Properties = currentToken.Properties
                        Set key=""
                        For {
                            Set value=accessToken.Properties.ResponseProperties.GetNext(.key)
                            If key="" Quit
                            Do ..SetTokenProperty(.propertiesNew,key,value)
                        }
                        Do ##class(OAuth2.Server.Auth).AddClaimValues(currentToken, currentToken.ClientId, accessToken.Username)
                        Set currentToken.Properties = propertiesNew
                        Set currentToken.Stage = "permission"
                        Do currentToken.Save()
                        // Skip permissions page
                        Set url = %request.URL_"?AuthorizationCode="_authorizationCode_"&Accept=Aceptar"
                        Set %response.Redirect = url
                    } Else {
                        Set sc = $$$ERROR($$$GeneralError, "Error getting oauth client")
                    }
                } Else {
                    Set sc = $$$ERROR($$$GeneralError, "Error getting current token")
                }
            } Else {
                Set sc = $$$ERROR($$$GeneralError, "Error getting cookie token")
                // Clear cookie access_token
                Set %response.Headers("Set-Cookie") = "access_token=; Path=/; Max-Age=0"
            }
            ZNspace currentNS
        } Else {
            Set sc = $$$ERROR($$$GeneralError, "Error cookie access_token missing")
        }
        
    } Catch ex {
        ZNspace currentNS
        If $$$ISOK(sc) {
            Set sc = ex.AsStatus()
        }
    }
    
    Quit sc
}

ClassMethod SetTokenProperty(Output properties As %OAuth2.Server.Properties, Name, Value, Type = "string") [ Internal, ServerOnly = 1 ]
{
    // Add claims and more
    Set tClaim = ##class(%OAuth2.Server.Claim).%New()
    Do properties.ResponseProperties.SetAt(Value,Name)
    Do properties.IntrospectionClaims.SetAt(tClaim,Name)
    Do properties.UserinfoClaims.SetAt(tClaim,Name)
    Do properties.JWTClaims.SetAt(tClaim,Name)
    Do properties.IDTokenClaims.SetAt(tClaim,Name)
    Do properties.SetClaimValue(Name,Value,Type)
    Quit $$$OK
}

 

Sobrescribe el método DisplayLogin

ClassMethod DisplayLogin(authorizationCode As %String, scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties, loginCount As %Integer = 1) As %Status
{
	Set sc = ..LoginFromCookie(authorizationCode)
	If $$$ISOK(sc) {
		Quit sc
	} Else {
		$$$LOGERROR($system.Status.GetErrorText(sc))
	}

	Quit ..DisplayLogin(authorizationCode, scope, properties, loginCount)
}

 

Veamos paso por paso que hace el método LoginFromCookie, supongamos que ya hemos hecho login con anterioridad y tenemos el token JWT de OpenID en una cookie llamada access_token.

  1. Obtiene la cookie access_token.
    Set cookieToken = %request.GetCookie("access_token")
  2. Cambia al namespace %SYS para usar los métodos de las librerías de Oauth.
    ZNspace "%SYS"
  3. Obtiene el token con la cookie, este método elimina los tokens expirados, nos vale para saber que el token es válido y no ha sido modificado.
    Set accessToken = ##class(OAuth2.Server.AccessToken).OpenByToken(cookieToken,.sc)
  4. Obtiene el token recién creado para el cliente de Oauth que solicita el login de usuario.
    Set currentToken = ##class(OAuth2.Server.AccessToken).OpenByCode(authorizationCode,.sc)
  5. Obtiene el cliente de Oauth.
    Set client = ##class(OAuth2.Server.Client).Open(currentToken.ClientId,.sc)
  6. Replica el usuario y las propiedades en el token nuevo.
    Set currentToken.Username = accessToken.Username
    #dim propertiesNew As %OAuth2.Server.Properties = currentToken.Properties
    Set key=""
    For {
    	Set value=accessToken.Properties.ResponseProperties.GetNext(.key)
    	If key="" Quit
    	Do ..SetTokenProperty(.propertiesNew,key,value)
    }
    Do ##class(OAuth2.Server.Auth).AddClaimValues(currentToken, currentToken.ClientId, accessToken.Username)
    Set currentToken.Properties = propertiesNew
  7. Salta el login y guarda los cambios del token.
    Set currentToken.Stage = "permission"
    Do currentToken.Save()
      En este punto podríamos llamar al método DisplayPermissions si queremos mostrar la pantalla de aceptación de permisos.  
  8. Saltar pantalla de permisos y enviar el código de autorización al callback del cliente de oauth
    Set url = %request.URL_"?AuthorizationCode="_authorizationCode_"&Accept=Aceptar"
    Set %response.Redirect = url

 

Otros apuntes

Nos ha pasado en otros entornos con versiones anteriores a la actual de IRIS que la redirección no funciona, una posible solución a esto es devolver un javascript que lo haga:

&html<<script type="text/javascript">
    window.location.href="#(url)#";
</script>>

 

Respecto a la pantalla de permisos lo ideal sería almacenar en una persistencia: Cliente de OAuth, Usuario y Scopes aceptados, y no mostrar la pantalla si los permisos fueron aceptados con anterioridad.

 

Este es mi primer post publicado, espero haberlo hecho de manera clara y que sea de utilidad para llevar el Oauth de IRIS al siguiente nivel.

Si has llegado hasta aquí agradecerte de corazón el tiempo de leer este post y si tienes alguna duda puedes hacérmela llegar en los comentarios.

Un saludo, Miguelio, Cysnet.

Comentarios (0)1
Inicie sesión o regístrese para continuar