JWT Implementation in Harbour

JWT Implementation in Harbour

Postby Otto » Fri Jun 21, 2024 9:04 am

Hello friends,

This Harbour project demonstrates the process of creating and validating a JSON Web Token (JWT).
The workflow involves initializing the necessary variables, creating the payload, encoding the payload into a JWT, and then validating the JWT.
If the JWT is valid, the payload is displayed; otherwise, an error message is shown.

Best regards,
Otto


Explanation of the program:

Initialization:

The program initializes by setting the secret key (cSecret) and the payload (hPayload), which contains information such as the

issued time (iat),
expiration time (exp),
issuer (iss), audience (aud),
resource (resource), and
user data (data).



Payload Creation:

The payload (hPayload) is prepared with the necessary details.
JWT Creation:

The jwtEncode function is called to create a JWT (JSON Web Token) using the payload and the secret key. This function encodes the header and payload in base64 URL format, then creates a signature using HMAC SHA-256 and encodes it in base64 URL format.
JWT Display:

The generated JWT is displayed and logged.
JWT Validation:

The jwtDecode function is called to validate the JWT using the secret key. This function decodes the JWT, verifies the signature, and if valid, converts the payload from JSON to a hash table.
Check Validity:

If the JWT is valid, the payload is displayed and logged. If not, an "Invalid JWT" message is logged.
End:

The process ends after displaying or logging the payload or the error message.


Code: Select all  Expand view  RUN

#include "fivewin.ch"

FUNCTION Main()
    LOCAL cSecret
    LOCAL hPayload
    LOCAL cJwt
    LOCAL hDecoded

    cSecret := "your_secret_key"
    hPayload := { 'iat' => UnixTime(), 'exp' => UnixTime() + 3600, ;
                  'iss' => 'your_domain.com', 'aud' => 'your_application', ;
                  'resource' => 'php1', 'data' => { 'username' => 'test' } }
     
     xbrowse(hPayload, "hPayload" )
    cJwt := jwtEncode(hPayload, cSecret)
   
    ? "JWT Token:", cJwt

    hDecoded := jwtDecode(cJwt, cSecret)
   
    IF ValType(hDecoded) == 'H'
         xbrowse( hDecoded , "hDecoded")
    ELSE
        ? "Invalid JWT"
    ENDIF
RETURN NIL

FUNCTION base64UrlEncode(cData)
    LOCAL cEncoded
    cEncoded := Base64Encode(cData)
    cEncoded := StrTran(cEncoded, "+", "-")
    cEncoded := StrTran(cEncoded, "/", "_")
    cEncoded := StrTran(cEncoded, "=", "")
RETURN cEncoded

FUNCTION base64UrlDecode(cData)
    LOCAL cDataFixed
    LOCAL nMod
    cDataFixed := StrTran(cData, "-", "+")
    cDataFixed := StrTran(cDataFixed, "_", "/")
    nMod := Len(cDataFixed) % 4
    IF nMod != 0
        cDataFixed += Replicate("=", 4 - nMod)
    ENDIF
RETURN Base64Decode(cDataFixed)

FUNCTION Base64Encode(cData)
    LOCAL cOut := ""
    LOCAL nInLen := Len(cData)
    LOCAL cIn := cData
    LOCAL nOutLen, nIndex, nPad
    LOCAL nBits, nValue

    // Base64 encoding table
    LOCAL cBase64 := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

    nOutLen := Int((nInLen + 2) / 3) * 4
    cOut := Space(nOutLen)
    nIndex := 1

    FOR nBits := 1 TO nInLen STEP 3
        nValue := Asc(SubStr(cIn, nBits, 1)) * 65536 + ;
                  IIF(nBits + 1 <= nInLen, Asc(SubStr(cIn, nBits + 1, 1)), 0) * 256 + ;
                  IIF(nBits + 2 <= nInLen, Asc(SubStr(cIn, nBits + 2, 1)), 0)

        SubStr(cOut, nIndex, 1) := SubStr(cBase64, Int(nValue / 262144) + 1, 1)
        SubStr(cOut, nIndex + 1, 1) := SubStr(cBase64, Int(Mod(nValue, 262144) / 4096) + 1, 1)
        SubStr(cOut, nIndex + 2, 1) := IIF(nBits + 1 <= nInLen, SubStr(cBase64, Int(Mod(nValue, 4096) / 64) + 1, 1), "=")
        SubStr(cOut, nIndex + 3, 1) := IIF(nBits + 2 <= nInLen, SubStr(cBase64, Mod(nValue, 64) + 1, 1), "=")

        nIndex += 4
    NEXT

RETURN cOut

FUNCTION Base64Decode(cData)
    LOCAL cOut := ""
    LOCAL nInLen := Len(cData)
    LOCAL cIn := cData
    LOCAL nOutLen, nIndex, nPad
    LOCAL nValue, nBits

    // Base64 decoding table
    LOCAL cBase64 := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    LOCAL aDecode := Array(256, 0)
    LOCAL i

    FOR i := 1 TO 64
        aDecode[Asc(SubStr(cBase64, i, 1))] := i - 1
    NEXT

    nOutLen := Int((nInLen + 3) / 4) * 3
    cOut := Space(nOutLen)
    nIndex := 1

    FOR nBits := 1 TO nInLen STEP 4
        nValue := aDecode[Asc(SubStr(cIn, nBits, 1))] * 262144 + ;
                  aDecode[Asc(SubStr(cIn, nBits + 1, 1))] * 4096 + ;
                  IIF(SubStr(cIn, nBits + 2, 1) != "=", aDecode[Asc(SubStr(cIn, nBits + 2, 1))] * 64, 0) + ;
                  IIF(SubStr(cIn, nBits + 3, 1) != "=", aDecode[Asc(SubStr(cIn, nBits + 3, 1))], 0)

        SubStr(cOut, nIndex, 1) := Chr(Int(nValue / 65536))
        IF SubStr(cIn, nBits + 2, 1) != "="
            SubStr(cOut, nIndex + 1, 1) := Chr(Int(Mod(nValue, 65536) / 256))
        ENDIF
        IF SubStr(cIn, nBits + 3, 1) != "="
            SubStr(cOut, nIndex + 2, 1) := Chr(Mod(nValue, 256))
        ENDIF

        nIndex += 3
    NEXT

RETURN RTrim(cOut)

FUNCTION hb_DateTime()
    RETURN Seconds()

FUNCTION UnixTime()
    LOCAL dEpoch := Ctod("1970-01-01")
    LOCAL dToday := Date()
    LOCAL nSeconds

    // Berechnung der Anzahl der Sekunden seit Mitternacht
    LOCAL cTime := Time()
    LOCAL nHours := Val(Left(cTime, 2))
    LOCAL nMinutes := Val(SubStr(cTime, 4, 2))
    LOCAL nSecondsPart := Val(Right(cTime, 2))

    nSeconds := (dToday - dEpoch) * 86400 + nHours * 3600 + nMinutes * 60 + nSecondsPart
RETURN nSeconds

FUNCTION jwtEncode(hPayload, cSecret)
    LOCAL hHeader
    LOCAL cHeader, cPayload
    LOCAL cBase64UrlHeader, cBase64UrlPayload
    LOCAL cSignature, cBase64UrlSignature

    hHeader := { 'typ' => 'JWT', 'alg' => 'HS256' }
    cHeader := hb_JsonEncode(hHeader)
    cPayload := hb_JsonEncode(hPayload)
    cBase64UrlHeader := base64UrlEncode(cHeader)
    cBase64UrlPayload := base64UrlEncode(cPayload)
    cSignature := hb_HMAC_SHA256(cBase64UrlHeader + "." + cBase64UrlPayload, cSecret)
    cBase64UrlSignature := base64UrlEncode(cSignature)

RETURN cBase64UrlHeader + "." + cBase64UrlPayload + "." + cBase64UrlSignature

FUNCTION jwtDecode(cJwt, cSecret)
    LOCAL aParts
    LOCAL cHeader, cPayload, cSignature
    LOCAL cBase64UrlHeader, cBase64UrlPayload, cBase64UrlSignature
    LOCAL cValidSignature
    LOCAL hDecoded

    aParts := hb_ATokens(cJwt, ".")
    IF Len(aParts) != 3
        RETURN NIL
    ENDIF

    cHeader := aParts[1]
    cPayload := aParts[2]
    cSignature := aParts[3]

    cBase64UrlHeader := base64UrlEncode(base64UrlDecode(cHeader))
    cBase64UrlPayload := base64UrlEncode(base64UrlDecode(cPayload))
    cBase64UrlSignature := base64UrlEncode(base64UrlDecode(cSignature))
    cValidSignature := base64UrlEncode(hb_HMAC_SHA256(cBase64UrlHeader + "." + cBase64UrlPayload, cSecret))

    IF cBase64UrlSignature == cValidSignature
        hDecoded := jsonToHash(base64UrlDecode(cPayload))
        RETURN hDecoded
    ELSE
        RETURN NIL
    ENDIF
RETURN NIL

FUNCTION jsonToHash(cJson)
    LOCAL hResult := {=>}  // Initialisierung als leeres Hash-Array
    LOCAL cKey, cValue, nPos, nEnd, cSubJson
    LOCAL cJsonTrimmed := Trim(cJson)
    LOCAL nLen := Len(cJsonTrimmed)
    LOCAL cChar
    LOCAL lIsArray := .F.

    IF Left(cJsonTrimmed, 1) == "[" .AND. Right(cJsonTrimmed, 1) == "]"
        cJsonTrimmed := SubStr(cJsonTrimmed, 2, nLen - 2)
        lIsArray := .T.
    ELSEIF Left(cJsonTrimmed, 1) == "{" .AND. Right(cJsonTrimmed, 1) == "}"
        cJsonTrimmed := SubStr(cJsonTrimmed, 2, nLen - 2)
    ELSE
        RETURN NIL
    ENDIF

    nPos := 1
    WHILE nPos <= Len(cJsonTrimmed)
        cChar := SubStr(cJsonTrimmed, nPos, 1)
        IF cChar == '"'
            nEnd := At('"', cJsonTrimmed, nPos + 1)
            cKey := SubStr(cJsonTrimmed, nPos + 1, nEnd - nPos - 1)
            nPos := At(":", cJsonTrimmed, nEnd) + 1
            nEnd := At(",", cJsonTrimmed, nPos)
            IF nEnd == 0
                nEnd := Len(cJsonTrimmed) + 1
            ENDIF
            cValue := Trim(SubStr(cJsonTrimmed, nPos, nEnd - nPos))
            IF Left(cValue, 1) == "{" .OR. Left(cValue, 1) == "["
                cSubJson := cValue
                cValue := jsonToHash(cSubJson)
            ELSE
                IF ValType(cValue) == "C" .AND. Left(cValue, 1) == '"' .AND. Right(cValue, 1) == '"'
                    cValue := SubStr(cValue, 2, Len(cValue) - 2)
                ELSE
                    cValue := Val(cValue)
                ENDIF
            ENDIF
            IF lIsArray
                AAdd(hResult, cValue)
            ELSE
                hResult[cKey] := cValue
            ENDIF
            nPos := nEnd + 1
        ELSE
            nPos++
        ENDIF
    ENDDO
RETURN hResult

 



Image
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
Otto
 
Posts: 6338
Joined: Fri Oct 07, 2005 7:07 pm

Re: JWT Implementation in Harbour

Postby Baxajaun » Fri Jun 21, 2024 10:04 am

Hi Otto !!!

Please, take a look Matteo's project https://github.com/matteobaccan/HarbourJwt.

Regards,
User avatar
Baxajaun
 
Posts: 968
Joined: Wed Oct 19, 2005 2:17 pm
Location: Gatika. Bizkaia

Re: JWT Implementation in Harbour

Postby Otto » Fri Jun 21, 2024 11:44 am

Hello Baxajaun,


Thank you for the information. I am familiar with the project and have already run websites with it. I use this project frequently.

However, I personally believe that OOP is not useful for web programming in the way I use mod_harbour.

As you might know, I have a preprocessor and a patcher. It's easiest to just patch in functions, which has many advantages.

One advantage is that you don't have to deal with the runtime overhead of objects. From my experience with mod_harbour, objects don't provide any benefit. You create them, and then they are already obsolete.

With traditional programming, you have the exact source code in front of you that you pass to mod_harbour and can simply trace it.

Many more users can collaborate on such a simple layout. We have overextended and over-engineered the simple Clipper code.

Remember how many people contributed to development when the code, despite having the same functionality, was still easy to read.

The speed of development is enhanced. AI can assist much better with functional programming than with objects.

My goal is to include as few libraries as possible alongside FiveWin/Harbour and for mod_harbour HTML/JS. This limits flexibility.

The idea to do it myself came to me because I made a program for JWT in PHP, and the AI suggested a library (Firebase).
This then caused problems, and the AI suggested that we use user-defined functions. Two hours later, I had a finished solution.

Then I thought I would see what happens if I had the PHP program rewritten in Harbour.
Sure, with Harbour you still have to intervene manually.

Best regards,
Otto


Please compare the two approaches. Do you really think that OOP makes sense here?


Image

Image
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
Otto
 
Posts: 6338
Joined: Fri Oct 07, 2005 7:07 pm

Re: JWT Implementation in Harbour

Postby Otto » Wed Jun 26, 2024 5:35 am

My posted code is for xHarbour.
Here is a HARBOUR version: https://mybergland.com/fwforum/jwthb.zip
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
Otto
 
Posts: 6338
Joined: Fri Oct 07, 2005 7:07 pm

Re: JWT Implementation in Harbour

Postby Otto » Wed Jun 26, 2024 5:40 am

It seems this new code is working for Harbour and xHarbour.

Code: Select all  Expand view  RUN


#include "fivewin.ch"

FUNCTION Main()
    LOCAL cSecret
    LOCAL hPayload
    LOCAL cJwt
    LOCAL hDecoded

    cSecret := "your_secret_key"
    hPayload := { 'iat' => UnixTime(), 'exp' => UnixTime() + 3600, ;
                  'iss' => 'your_domain.com', 'aud' => 'your_application', ;
                  'resource' => 'php1', 'data' => { 'username' => 'test' } }
   
    xbrowse(hPayload, "hPayload" )
    cJwt := jwtEncode(hPayload, cSecret)
   
    ? "JWT Token:", cJwt

    hDecoded := jwtDecode(cJwt, cSecret)
   
    IF ValType(hDecoded) == 'H'
         xbrowse( hDecoded , "hDecoded")
    ELSE
        ? "Invalid JWT"
    ENDIF
RETURN NIL

FUNCTION base64UrlEncode(cData)
    LOCAL cEncoded
    cEncoded := Base64Encode(cData)
    cEncoded := StrTran(cEncoded, "+", "-")
    cEncoded := StrTran(cEncoded, "/", "_")
    cEncoded := StrTran(cEncoded, "=", "")
RETURN cEncoded

FUNCTION base64UrlDecode(cData)
    LOCAL cDataFixed
    LOCAL nMod
    cDataFixed := StrTran(cData, "-", "+")
    cDataFixed := StrTran(cDataFixed, "_", "/")
    nMod := Len(cDataFixed) % 4
    IF nMod != 0
        cDataFixed += Replicate("=", 4 - nMod)
    ENDIF
RETURN Base64Decode(cDataFixed)

 
FUNCTION Base64Encode(cData)
    LOCAL cOut := ""
    LOCAL nInLen := Len(cData)
    LOCAL cIn := cData
    LOCAL nOutLen, nIndex, nPad
    LOCAL nBits, nValue

    // Base64 encoding table
    LOCAL cBase64 := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

    nOutLen := Int((nInLen + 2) / 3) * 4
    cOut := Space(nOutLen)
    nIndex := 1

    FOR nBits := 1 TO nInLen STEP 3
        nValue := Asc(SubStr(cIn, nBits, 1)) * 65536 + ;
                  IIF(nBits + 1 <= nInLen, Asc(SubStr(cIn, nBits + 1, 1)), 0) * 256 + ;
                  IIF(nBits + 2 <= nInLen, Asc(SubStr(cIn, nBits + 2, 1)), 0)

        cOut := Stuff(cOut, nIndex, 1, SubStr(cBase64, Int(nValue / 262144) + 1, 1))
        cOut := Stuff(cOut, nIndex + 1, 1, SubStr(cBase64, Int(Mod(nValue, 262144) / 4096) + 1, 1))
        cOut := Stuff(cOut, nIndex + 2, 1, IIF(nBits + 1 <= nInLen, SubStr(cBase64, Int(Mod(nValue, 4096) / 64) + 1, 1), "="))
        cOut := Stuff(cOut, nIndex + 3, 1, IIF(nBits + 2 <= nInLen, SubStr(cBase64, Mod(nValue, 64) + 1, 1), "="))

        nIndex += 4
    NEXT

RETURN cOut

FUNCTION Base64Decode(cData)
    LOCAL cOut := ""
    LOCAL nInLen := Len(cData)
    LOCAL cIn := cData
    LOCAL nOutLen, nIndex, nPad
    LOCAL nValue, nBits

    // Base64 decoding table
    LOCAL cBase64 := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    LOCAL aDecode := Array(256, 0)
    LOCAL i

    FOR i := 1 TO 64
        aDecode[Asc(SubStr(cBase64, i, 1))] := i - 1
    NEXT

    nOutLen := Int((nInLen + 3) / 4) * 3
    cOut := Space(nOutLen)
    nIndex := 1

    FOR nBits := 1 TO nInLen STEP 4
        nValue := aDecode[Asc(SubStr(cIn, nBits, 1))] * 262144 + ;
                  aDecode[Asc(SubStr(cIn, nBits + 1, 1))] * 4096 + ;
                  IIF(SubStr(cIn, nBits + 2, 1) != "=", aDecode[Asc(SubStr(cIn, nBits + 2, 1))] * 64, 0) + ;
                  IIF(SubStr(cIn, nBits + 3, 1) != "=", aDecode[Asc(SubStr(cIn, nBits + 3, 1))], 0)

        cOut := Stuff(cOut, nIndex, 1, Chr(Int(nValue / 65536)))
        IF SubStr(cIn, nBits + 2, 1) != "="
            cOut := Stuff(cOut, nIndex + 1, 1, Chr(Int(Mod(nValue, 65536) / 256)))
        ENDIF
        IF SubStr(cIn, nBits + 3, 1) != "="
            cOut := Stuff(cOut, nIndex + 2, 1, Chr(Mod(nValue, 256)))
        ENDIF

        nIndex += 3
    NEXT

RETURN RTrim(cOut)


FUNCTION hb_DateTime()
    RETURN Seconds()

FUNCTION UnixTime()
    LOCAL dEpoch := Ctod("1970-01-01")
    LOCAL dToday := Date()
    LOCAL nSeconds

    // Berechnung der Anzahl der Sekunden seit Mitternacht
    LOCAL cTime := Time()
    LOCAL nHours := Val(Left(cTime, 2))
    LOCAL nMinutes := Val(SubStr(cTime, 4, 2))
    LOCAL nSecondsPart := Val(Right(cTime, 2))

    nSeconds := (dToday - dEpoch) * 86400 + nHours * 3600 + nMinutes * 60 + nSecondsPart
RETURN nSeconds

FUNCTION jwtEncode(hPayload, cSecret)
    LOCAL hHeader
    LOCAL cHeader, cPayload
    LOCAL cBase64UrlHeader, cBase64UrlPayload
    LOCAL cSignature, cBase64UrlSignature

    hHeader := { 'typ' => 'JWT', 'alg' => 'HS256' }
    cHeader := hb_JsonEncode(hHeader)
    cPayload := hb_JsonEncode(hPayload)
    cBase64UrlHeader := base64UrlEncode(cHeader)
    cBase64UrlPayload := base64UrlEncode(cPayload)
    cSignature := hb_HMAC_SHA256(cBase64UrlHeader + "." + cBase64UrlPayload, cSecret)
    cBase64UrlSignature := base64UrlEncode(cSignature)

RETURN cBase64UrlHeader + "." + cBase64UrlPayload + "." + cBase64UrlSignature

FUNCTION jwtDecode(cJwt, cSecret)
    LOCAL aParts
    LOCAL cHeader, cPayload, cSignature
    LOCAL cBase64UrlHeader, cBase64UrlPayload, cBase64UrlSignature
    LOCAL cValidSignature
    LOCAL hDecoded

    aParts := hb_ATokens(cJwt, ".")
    IF Len(aParts) != 3
        RETURN NIL
    ENDIF

    cHeader := aParts[1]
    cPayload := aParts[2]
    cSignature := aParts[3]

    cBase64UrlHeader := base64UrlEncode(base64UrlDecode(cHeader))
    cBase64UrlPayload := base64UrlEncode(base64UrlDecode(cPayload))
    cBase64UrlSignature := base64UrlEncode(base64UrlDecode(cSignature))
    cValidSignature := base64UrlEncode(hb_HMAC_SHA256(cBase64UrlHeader + "." + cBase64UrlPayload, cSecret))

    IF cBase64UrlSignature == cValidSignature
        hDecoded := jsonToHash(base64UrlDecode(cPayload))
        RETURN hDecoded
    ELSE
        RETURN NIL
    ENDIF
RETURN NIL

FUNCTION jsonToHash(cJson)
    LOCAL hResult := {=>}  // Initialize as an empty hash array
    LOCAL cKey, cValue, nPos, nEnd, cSubJson
    LOCAL cJsonTrimmed := Trim(cJson)
    LOCAL nLen := Len(cJsonTrimmed)
    LOCAL cChar
    LOCAL lIsArray := .F.

    IF Left(cJsonTrimmed, 1) == "[" .AND. Right(cJsonTrimmed, 1) == "]"
        cJsonTrimmed := SubStr(cJsonTrimmed, 2, nLen - 2)
        lIsArray := .T.
    ELSEIF Left(cJsonTrimmed, 1) == "{" .AND. Right(cJsonTrimmed, 1) == "}"
        cJsonTrimmed := SubStr(cJsonTrimmed, 2, nLen - 2)
    ELSE
        RETURN NIL
    ENDIF

    nPos := 1
    WHILE nPos <= Len(cJsonTrimmed)
        cChar := SubStr(cJsonTrimmed, nPos, 1)
        IF cChar == '"'
            nEnd := At('"', SubStr(cJsonTrimmed, nPos + 1)) + nPos
            cKey := SubStr(cJsonTrimmed, nPos + 1, nEnd - nPos - 1)
            nPos := nEnd + 1
            nPos := At(":", cJsonTrimmed) + nPos - 1
            nEnd := At(",", SubStr(cJsonTrimmed, nPos))
            IF nEnd == 0
                nEnd := Len(cJsonTrimmed) + 1
            ELSE
                nEnd += nPos - 1
            ENDIF
            cValue := Trim(SubStr(cJsonTrimmed, nPos, nEnd - nPos))
            IF Left(cValue, 1) == "{" .OR. Left(cValue, 1) == "["
                cSubJson := cValue
                cValue := jsonToHash(cSubJson)
            ELSE
                IF ValType(cValue) == "C" .AND. Left(cValue, 1) == '"' .AND. Right(cValue, 1) == '"'
                    cValue := SubStr(cValue, 2, Len(cValue) - 2)
                ELSE
                    cValue := Val(cValue)
                ENDIF
            ENDIF
            IF lIsArray
                AAdd(hResult, cValue)
            ELSE
                hResult[cKey] := cValue
            ENDIF
            nPos := nEnd + 1
        ELSE
            nPos++
        ENDIF
    ENDDO
RETURN hResult



 
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
Otto
 
Posts: 6338
Joined: Fri Oct 07, 2005 7:07 pm


Return to FiveWin for Harbour/xHarbour

Who is online

Users browsing this forum: No registered users and 54 guests