Página 1 de 1

JWT Implementation for Harbour - (c) Matteo Baccan

Publicado: Dom Mar 27, 2022 6:27 pm
por Baxajaun
Buenas tardes !!!

Una contribución interesante

Código: Seleccionar todo

JWT Implementation for Harbour

A simple library to work with JSON Web Token and JSON Web Signature for Harbour language. You can find more information about JWT on the https://jwt.io/.

Harbour JWT supports the following algorithms:

HS256
HS384
HS512
HarbourJwt

Código: Seleccionar todo

/*
 * Copyright (c) 2019 Matteo Baccan
 * https://www.baccan.it
 *
 * Distributed under the GPL v3 software license, see the accompanying
 * file LICENSE or http://www.gnu.org/licenses/gpl.html.
 *
 */
/**
 * JWT Implementation
 *
 * https://datatracker.ietf.org/doc/html/rfc7519
 *
 */
#include "hbclass.ch"

CLASS JWT

HIDDEN:

  CLASSDATA cSecret
  CLASSDATA aHeader
  CLASSDATA aPayload
  CLASSDATA cError

  METHOD Base64UrlEncode( cData )
  METHOD Base64UrlDecode( cData )
  METHOD ByteToString( cData )
  METHOD GetSignature( cHeader, cPayload, cSecret, cAlgorithm )
  METHOD CheckPayload(aPayload, cKey)

EXPORTED:

  METHOD New() CONSTRUCTOR

  // Header
  METHOD SetType( cType )
  METHOD GetType()                          INLINE ::aHeader[ 'typ' ]
  METHOD SetContentType( cContentType )     INLINE ::aHeader[ 'cty' ] :=  cContentType
  METHOD GetContentType()                   INLINE ::aHeader[ 'cty' ]
  METHOD SetAlgorithm( cAlgorithm )
  METHOD GetAlgorithm()                     INLINE ::aHeader[ 'alg' ]

  // Payload
  METHOD SetIssuer( cIssuer )               INLINE ::SetPayloadData('iss', cIssuer)
  METHOD GetIssuer()                        INLINE ::GetPayloadData('iss')
  METHOD SetSubject( cSubject )             INLINE ::SetPayloadData('sub', cSubject)
  METHOD GetSubject()                       INLINE ::GetPayloadData('sub')
  METHOD SetAudience( cAudience )           INLINE ::SetPayloadData('aud', cAudience)
  METHOD GetAudience()                      INLINE ::GetPayloadData('aud')
  METHOD SetExpration( nExpiration )        INLINE ::SetPayloadData('exp', nExpiration)
  METHOD GetExpration()                     INLINE ::GetPayloadData('exp')
  METHOD SetNotBefore( nNotBefore )         INLINE ::SetPayloadData('nbf', nNotBefore)
  METHOD GetNotBefore()                     INLINE ::GetPayloadData('nbf')
  METHOD SetIssuedAt( nIssuedAt )           INLINE ::SetPayloadData('iat', nIssuedAt)
  METHOD GetIssuedAt()                      INLINE ::GetPayloadData('iat')
  METHOD SetJWTId( cJWTId )                 INLINE ::SetPayloadData('jti', cJWTId)
  METHOD GetJWTId()                         INLINE ::GetPayloadData('jti')

  // Payload methods
  METHOD SetPayloadData( cKey, uValue )     INLINE IF( uValue==NIL, hb_HDel(::aPayload,cKey), ::aPayload[cKey] := uValue)
  METHOD GetPayloadData( cKey )             INLINE IF( hb_HHasKey(::aPayLoad,cKey), ::aPayload[cKey], NIL )

  // Secret
  METHOD SetSecret( cSecret )               INLINE ::cSecret := cSecret
  METHOD GetSecret()                        INLINE ::cSecret

  // Error
  METHOD GetError()                         INLINE ::cError

  // Cleanup: aHeader, aPayload, cError, cSecret
  METHOD Reset()

  // Encode a JWT and return it
  METHOD Encode()

  // Decode a JWT
  METHOD Decode( cJWT )

  // Decode a JWT
  METHOD Verify( cJWT )

  // Getter internal data with internal exposion
  METHOD GetPayload()                       INLINE hb_hClone(::aPayload)
  METHOD GetHeader()                        INLINE hb_hClone(::aHeader)

  // Helper method for expiration setting
  METHOD GetSeconds()

  // Versione
  METHOD GetVersion()                       INLINE "1.0.2"

ENDCLASS

METHOD New() CLASS JWT
  ::Reset()
RETU SELF

// Optional
METHOD SetType( cType ) CLASS JWT
  LOCAL bRet := .F.

  IF cType=="JWT"
      ::aHeader[ 'typ' ] := cType
  ELSE
      bRet := .F.
      ::cError := "Invalid type [" +cType +"]"
  ENDIF

RETU bRet

// Mandatory
METHOD SetAlgorithm( cAlgorithm ) CLASS JWT
  LOCAL bRet := .F.

  IF cAlgorithm=="HS256" .OR. cAlgorithm=="HS384" .OR. cAlgorithm=="HS512"
      ::aHeader[ 'alg' ] := cAlgorithm
  ELSE
      bRet := .F.
      ::cError := "Invalid algorithm [" +cAlgorithm +"]"
  ENDIF

RETU bRet

METHOD Reset() CLASS JWT

  ::aHeader   := {=>}
  ::aPayload := {=>}
  ::cError  := ''
  ::cSecret  := ''

RETU NIL


METHOD Encode() CLASS JWT

  LOCAL cHeader
  LOCAL cPayload
  LOCAL cSignature

  //  Encode header
  cHeader     := ::Base64UrlEncode( hb_jsonEncode( ::aHeader ) )

  // Encode payload
  cPayload    := ::Base64UrlEncode( hb_jsonEncode( ::aPayload ) )

  //  Make signature
  cSignature := ::GetSignature( cHeader, cPayload, ::cSecret, ::aHeader[ 'alg' ] )

//  Return JWT
RETU cHeader + '.' + cPayload + '.' + cSignature

METHOD Base64UrlEncode( cData ) CLASS JWT
RETU hb_StrReplace( hb_base64Encode( cData ), "+/=", { "-", "_", "" } )

METHOD Base64UrlDecode( cData ) CLASS JWT
RETU hb_base64Decode( hb_StrReplace( cData, "-_", "+/" ) )

METHOD ByteToString( cData ) CLASS JWT
   LOCAL cRet := SPACE(LEN(cData)/2)
   LOCAL nLen := LEN( cData )
   LOCAL nX, nNum

   cData := UPPER(cData)
   FOR nX := 1 TO nLen STEP 2
      nNum := ( AT( SubStr( cData, nX  , 1 ), "0123456789ABCDEF" ) - 1 ) * 16
      nNum += AT( SubStr( cData, nX+1, 1 ), "0123456789ABCDEF" ) - 1
      HB_BPOKE( @cRet, (nX+1)/2, nNum )
   NEXT

RETU cRet

METHOD GetSignature( cHeader, cPayload, cSecret, cAlgorithm ) CLASS JWT
  LOCAL cSignature := ""

  DO CASE
     CASE cAlgorithm=="HS256"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA256( cHeader + '.' + cPayload, cSecret ) ) )
     CASE cAlgorithm=="HS384"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA384( cHeader + '.' + cPayload, cSecret ) ) )
     CASE cAlgorithm=="HS512"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA512( cHeader + '.' + cPayload, cSecret ) ) )
     OTHERWISE
         ::cError := "INVALID ALGORITHM"
  ENDCASE
RETU cSignature

METHOD Decode( cJWT ) CLASS JWT

  LOCAL aJWT

  // Reset Object
  ::Reset()

  // Check JWT
  IF VALTYPE(cJWT)!="C"
      ::cError := "Invalid JWT: not character ["+VALTYPE(cJWT)+"]"
      RETU .F.
  ENDIF

  //  Split JWT
  aJWT := HB_ATokens( cJWT, '.' )
  IF LEN(aJWT) <> 3
      ::cError := "Invalid JWT"
      RETU .F.
  ENDIF

  // Explode header
  ::aHeader   := hb_jsonDecode( ::Base64UrlDecode( aJWT[1] ))

  // Exploce payload
  ::aPayload  := hb_jsonDecode( ::Base64UrlDecode( aJWT[2] ))

RETU .T.

METHOD Verify( cJWT ) CLASS JWT

  LOCAL aJWT, aHeader, aPayload
  LOCAL cSignature, cNewSignature

  // Check JWT
  IF VALTYPE(cJWT)!="C"
      ::cError := "Invalid JWT: not character ["+VALTYPE(cJWT)+"]"
      RETU .F.
  ENDIF

  //  Split JWT
  aJWT := HB_ATokens( cJWT, '.' )
  IF LEN(aJWT) <> 3
      ::cError := "Invalid JWT"
      RETU .F.
  ENDIF

  // Explode header
  aHeader   := hb_jsonDecode( ::Base64UrlDecode( aJWT[1] ))

  // Check aHeader
  IF VALTYPE(aHeader)!="H"
      ::cError := "Invalid JWT: header not base64"
      RETU .F.
  ENDIF

  // Exploce payload
  aPayload   := hb_jsonDecode( ::Base64UrlDecode( aJWT[2] ))

  // Check aPayload
  IF VALTYPE(aPayload)!="H"
      ::cError := "Invalid JWT: payload not base64"
      RETU .F.
  ENDIF

  // Get signature
  cSignature  := aJWT[3]

  // Calculate new sicnature
  cNewSignature   := ::GetSignature( aJWT[1], aJWT[2], ::cSecret, aHeader[ 'alg' ] )
  IF ( cSignature != cNewSignature )
    ::cError := "Invalid signature"
    RETU .F.
  ENDIF

  // Check Issuer
  IF !::CheckPayload(aPayload, 'iss')
     ::cError := "Different issuer"
     RETU .F.
  ENDIF

  // Check Subject
  IF !::CheckPayload(aPayload, 'sub')
     ::cError := "Different subject"
     RETU .F.
  ENDIF

  // Check Audience
  IF !::CheckPayload(aPayload, 'aud')
     ::cError := "Different audience"
     RETU .F.
  ENDIF

  // Check expiration
  IF hb_HHasKey(aPayLoad,'exp')
     IF aPayLoad[ 'exp' ] < ::GetSeconds()
       ::cError := "Token expired"
       RETU .F.
     ENDIF
  ENDIF

  // Check not before
  IF hb_HHasKey(aPayLoad,'nbf')
     IF aPayLoad[ 'nbf' ] > ::GetSeconds()
       ::cError := "Token not valid until:" +STR(aPayLoad[ 'nbf' ])
       RETU .F.
     ENDIF
  ENDIF

  // Check issuedAt
  IF hb_HHasKey(aPayLoad,'iat')
     IF aPayLoad[ 'iat' ] > ::GetSeconds()
       ::cError := "Token issued in future:" +STR(aPayLoad[ 'iat' ])
       RETU .F.
     ENDIF
  ENDIF

  // Check JWT id
  IF !::CheckPayload(aPayload, 'jti')
     ::cError := "Different JWT id"
     RETU .F.
  ENDIF

  // Check Type
  IF !::CheckPayload(aPayload, 'typ')
     ::cError := "Different JWT type"
     RETU .F.
  ENDIF

RETU .T.

METHOD GetSeconds() CLASS JWT

  LOCAL posixday := date() - STOD("19700101")
  LOCAL cTime := time()
  LOCAL posixsec := posixday * 24 * 60 * 60

RETU posixsec + (int(val(substr(cTime,1,2))) * 3600) + (int(val(substr(cTime,4.2))) * 60) + ( int(val(substr(cTime,7,2))) )

METHOD CheckPayload(aPayload, cKey)
  IF hb_HHasKey(aPayLoad,cKey) .AND. hb_HHasKey(::aPayLoad,cKey)
     IF aPayLoad[ cKey ] != ::aPayLoad[ cKey ]
       RETU .F.
     ENDIF
  ELSEIF hb_HHasKey(aPayLoad,cKey) .OR. hb_HHasKey(::aPayLoad,cKey)
     RETU .F.
  ENDIF
RETU .T.
Muchas gracias Matteo !!!

Saludos,

Re: JWT Implementation for Harbour - (c) Matteo Baccan

Publicado: Dom Mar 27, 2022 10:19 pm
por Cristobal
Muy bien, gracias Matteo
Añadir que creo recordar que Carles Aubia también tiene una clase para mod-harbour en relación a este tema

Re: JWT Implementation for Harbour - (c) Matteo Baccan

Publicado: Lun Mar 28, 2022 7:06 am
por Baxajaun
Hola Cristóbal !

No sabía que Charly tenía un clase para implementar JWT.

Muchas gracias.

Saludos,

Re: JWT Implementation for Harbour - (c) Matteo Baccan

Publicado: Lun Nov 28, 2022 5:01 pm
por gvaronas
Buen día estimado Cristóbal, dónde se puede ver la clase jwt de Carles Aubia?
De antemano muchas Gracias por la atención.
Cristobal escribió: Dom Mar 27, 2022 10:19 pm Muy bien, gracias Matteo
Añadir que creo recordar que Carles Aubia también tiene una clase para mod-harbour en relación a este tema