Parse JWT tokens in Azure Logic Apps

Recently I was working on a project where I had a JavaScript frontend website which was controlled by a backend which ran on Azure Logic Apps. Some of the logic apps where only allowed to be accessed by certain people. So I used the JavaScript MSAL library to authenticate my users to Azure AD. This provided me with a JWT token which I could then use in the azure logic app to check if the user was allowed to access this logic app.

Making sure the logic app could only be triggered with a valid token

We want the logic app to only trigger if someone with a valid token starts it. To do this we use the authentication option in the logic apps.

For the issuer we enter “https://login.microsoftonline.com/<YOUR DIRECTORY ID>/v2.0” and for the Audience we enter the Application ID. Both of these values can be found in the app registration you had to make to use MSAL.
Next up we want to make sure we get the JWT token in our actual logic app. To do this we go to the logic app code view and look for the trigger in our logic app (assuming it’s an HTTP trigger).

In the trigger we add the line
"operationOptions": "IncludeAuthorizationHeadersInOutputs"
This will add the authentication header in the request data.
Now when the logic app is called (note that I will explain later how to call it properly) it will show the token, but this token is base64url encoded. So to make sure we can read the data inside we need to parse it.

Creating an Child Logic App to Parse JWT tokens

In one of my previous blogs I wrote about using child logic apps to do parallel processing with logic apps. In this case we use the child logic app to make sure this process can be reused in multiple logic apps.
The logic app we create to parse the jwt token is actually very simple and looks like this:

The body of the request has the following schema.

{
    "properties": {
        "Authorization": {
            "type": "string"
        }
    },
    "type": "object"
}

In the response you see an expression. This expression is as follows:

    if(
        equals(
            mod(
                length(
                    split(triggerBody()?['Authorization'],'.')[1]
                ),
                4
            ),
            0
        ),
        split(triggerBody()?['Authorization'],'.')[1]
        ,
        if(
            equals(
                mod(
                    length(
                        split(triggerBody()?['Authorization'],'.')[1]
                    ),
                    4
                ),
                1
            ),
            concat(
                split(triggerBody()?['Authorization'],'.')[1],
                '==='
                
            )
            ,
            if(
                equals(
                    mod(
                        length(
                            split(triggerBody()?['Authorization'],'.')[1]
                        ),
                        4
                    ),
                    2
                ),
                concat(
                    split(triggerBody()?['Authorization'],'.')[1],
                    '=='
                    
                )
                ,
                concat(
                    split(triggerBody()?['Authorization'],'.')[1],
                    '='
                    
                )
            )
        )
    )

What this expression does is splitting the JWT token by the . separator and then appending the “=” sign to make sure the length is a multiplication of 4. This is done in order for the logic apps to recognize it as a valid base64 string. When returning the result logic apps will automatically parse the base64 due to the implicit data type conversion.

Using the child logic app in your main logic app

Now let’s go back to our main logic app and look at how to use this.
At the start of our logic app we want to add the following steps:

You see that after the request we call the child logic app and give the Headers as input. This will give use the base64 decoded payload of the JWT token back. Next up we put this into a ParseJSON step to make sure we can use these value. Depending on which attributes you want to use you can alter the schema for the parse JSON, but for a normal azure AD JWT token you can use this schema:

{
    "properties": {
        "aio": {
            "type": "string"
        },
        "aud": {
            "type": "string"
        },
        "exp": {
            "type": "integer"
        },
        "iat": {
            "type": "integer"
        },
        "iss": {
            "type": "string"
        },
        "name": {
            "type": "string"
        },
        "nbf": {
            "type": "integer"
        },
        "nonce": {
            "type": "string"
        },
        "oid": {
            "type": "string"
        },
        "preferred_username": {
            "type": "string"
        },
        "rh": {
            "type": "string"
        },
        "roles": {
            "items": {
                "type": "string"
            },
            "type": "array"
        },
        "sub": {
            "type": "string"
        },
        "tid": {
            "type": "string"
        },
        "uti": {
            "type": "string"
        },
        "ver": {
            "type": "string"
        }
    },
    "type": "object"
}

Now you have the parse JWT in your logic app and you can do something like this:

Here I use the condition:
or(contains(body('Parse_JSON')?['roles'],'admin'),contains(body('Parse_JSON')?['roles'],'operator'))
where “admin” and “operator” are two roles I declared in my azure AD app. By checking if the roles attribute of the JWT token contains any of these I know the user is allowed to trigger this logic app. If not we return a 401 response with a custom message. If true I can return a 200 message with the date they requested.

How to call the logic app

To call the logic app using a Azure AD token you need to mind 1 specific thing. If you take the webhook URL from your request step in the logic app it looks something like this:

https://prod-111.westeurope.logic.azure.com:443/workflows/<id>/triggers/manual/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=<signature>

You want to remove the part after the api-version because this is used to authenticate with a other token. So the URL will become:

https://prod-111.westeurope.logic.azure.com:443/workflows/<id>/triggers/manual/paths/invoke?api-version=2016-10-01

If you call this URL with a tool like postman and make sure the authorization header is set to Bearer with the token entered you will see that your logic app will work.