Secure the Todo Application
In this getting started exercise, you will enhance the previously built Todo application to secure the Tasks API using JWT access tokens.
Zilla has the concept of a guard that can be defined to control access to any route in the bindings configuration.
In this guide, you will use the JWT guard to enforce authorization of the read:tasks and write:tasks roles when calling the Tasks API.

Prerequisites

Step 1: Zilla

In Build a Todo Application, you defined a Tasks API to send commands to the Todo service via Kafka and retrieve a continuously updated stream of tasks from Kafka as needed by the Tasks UX.
Now we secure the Tasks API by requiring the caller to have read:tasks role for the GET request, and write:tasks role for the POST, PUT and DELETE requests.

Configure Zilla

The Zilla engine configuration defines a flow of named bindings representing each step in the pipeline as inbound network traffic is decoded and transformed then encoded into outbound network traffic as needed.
In Build the Todo Application, Step 3: Zilla, you created zilla.json that defined the Tasks API without security.
zilla.json (starting point)
When routing at each binding, Zilla can guard a route to require that specific roles have been granted to the caller. If the caller does not have the required roles, then the route is ignored. If no routes are viable, then the HTTP request fails with 404 Not Found.
Zilla trusts JWT tokens based on the token issuer, audience and public key of the token provider.
In this example, tokens are issued by Auth0 at https://aklivity.us.auth0.com/ and the intended token usage, or audience, is this local Todo app Tasks API at http://localhost:8080/tasks.
Download the token provider public keys from Auth0 so that you can use them to configure Zilla.
curl -s https://aklivity.us.auth0.com/.well-known/jwks.json | jq .keys
[
{
"alg": "RS256",
"kty": "RSA",
"use": "sig",
"n": "xpUpx4ytuhi0Tz4_l7qqigo_CleAGBs7zalBLHR68tRz3EM2rf6JZapeT7vA6FcdGJskcqNSEYZJsbX0RWsqH_2WwXKOV8HOJJ_XrUmWy1Eeeco8nqM7NoImvubQ_3pxwGq2RaW4Ll3jZ90tWEWoGlk9qo_oJc2WKfnHSjpzuQxX0v5xKxJ3qJN9-SzJ-hit89Uj67o9aC5qOYbZTNgYDOyuawhAN3MlVF_twj7iqogpJprQEeLZTMpsmQbx0DvAju4Za_edJXAkVQeAeQq04SgLU0cagEmk7raNAuk19mWjPAPDq8OldVoecxtsqCGF_I17xdWozI98tJPDS00YWw",
"e": "AQAB",
"kid": "x9YmmTzyWUxAj6683CkM-",
"x5t": "xKsfyirPoDUfrpgvjnKI5wmYzBg",
"x5c": [
"MIIDBTCCAe2gAwIBAgIJfcoikPv8CQX+MA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNVBAMTFWFrbGl2aXR5LnVzLmF1dGgwLmNvbTAeFw0yMjA1MzEyMTI5MzZaFw0zNjAyMDcyMTI5MzZaMCAxHjAcBgNVBAMTFWFrbGl2aXR5LnVzLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMaVKceMrboYtE8+P5e6qooKPwpXgBgbO82pQSx0evLUc9xDNq3+iWWqXk+7wOhXHRibJHKjUhGGSbG19EVrKh/9lsFyjlfBziSf161JlstRHnnKPJ6jOzaCJr7m0P96ccBqtkWluC5d42fdLVhFqBpZPaqP6CXNlin5x0o6c7kMV9L+cSsSd6iTffksyfoYrfPVI+u6PWguajmG2UzYGAzsrmsIQDdzJVRf7cI+4qqIKSaa0BHi2UzKbJkG8dA7wI7uGWv3nSVwJFUHgHkKtOEoC1NHGoBJpO62jQLpNfZlozwDw6vDpXVaHnMbbKghhfyNe8XVqMyPfLSTw0tNGFsCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUUONbQh3M0n4qXgTRDu7F1XgFSN0wDgYDVR0PAQH/BAQDAgKEMA0GCSqGSIb3DQEBCwUAA4IBAQBdskJlljltBm+JPPEStfa264aKiKvdyjSuwQcvrqXPm5q5/GEmKjMdG3c+3R6BmgWOKCHL3Bg70aFWidqy6sYfbdZ806JctN2FGsJUa5hSTZSjkhn+VkER16/6iCNr5e/KVnZwkWd5U05asshoaMugHTLlFHISxJ2qMUKda2Wi3tkf7eMzc7+1BReY+6etT0ZMf1st1BPalj41cnaBSiLcO67s7XIvH2gkTdYABbzIwXBLuWvQUZ5pX73JdCOuXhfXN/3oE3ICZfOJjGcqeg4eAO8Ns/NFyho8U2xFP8pqhrHqyvxkNHh7eJZaB5gdfUodL4Ldtkmz351zFBCRey0g"
]
},
{
"alg": "RS256",
"kty": "RSA",
"use": "sig",
"n": "zlx0IeG8Gzw77BPCuR9MvZVSbSTRkfkcAklS23roIr4mxLr8m5I2Q2bOj56qHztV8frPVTZ0GyesxwScdaSRkNomF2nnzibWRabcek50UmHbpgeA3dXAmxCMUR-Tah0w-eCSsbXu6PbtpWmLm_7niSJnRed45x7b876E2G_DitQFErGMZaLmSG7ewF0aHz-gn37Uio2l71-oXnvshpC7Y17-TUYUDKIiko6P1UKsY7fUBEaZk9-he6Khsny7KYUkcZ1F45q0WKlGwiZyZjU8jPhNGPIc8qj6QmjEhJ07IzjDR4x8pZN3F4go6mKB1thWWhGWrkaLI8UmluHDcD33RQ",
"e": "AQAB",
"kid": "sBT98m8u8lpwvV_Cm5ZAX",
"x5t": "vfM0DKkRNUmYxs13GJnM-NlADuU",
"x5c": [
"MIIDBTCCAe2gAwIBAgIJevGCW7bs17g5MA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNVBAMTFWFrbGl2aXR5LnVzLmF1dGgwLmNvbTAeFw0yMjA1MzEyMTI5MzZaFw0zNjAyMDcyMTI5MzZaMCAxHjAcBgNVBAMTFWFrbGl2aXR5LnVzLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5cdCHhvBs8O+wTwrkfTL2VUm0k0ZH5HAJJUtt66CK+JsS6/JuSNkNmzo+eqh87VfH6z1U2dBsnrMcEnHWkkZDaJhdp584m1kWm3HpOdFJh26YHgN3VwJsQjFEfk2odMPngkrG17uj27aVpi5v+54kiZ0XneOce2/O+hNhvw4rUBRKxjGWi5khu3sBdGh8/oJ9+1IqNpe9fqF577IaQu2Ne/k1GFAyiIpKOj9VCrGO31ARGmZPfoXuiobJ8uymFJHGdReOatFipRsImcmY1PIz4TRjyHPKo+kJoxISdOyM4w0eMfKWTdxeIKOpigdbYVloRlq5GiyPFJpbhw3A990UCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUt/86Q4B1Xalsr8077ehQ9iqi3rkwDgYDVR0PAQH/BAQDAgKEMA0GCSqGSIb3DQEBCwUAA4IBAQBC6ejNbo9u7+qFvh+j1Zh7YZW+uz06Gd4WnwCU+926JP6U7NMNt3guvPVPYtFXSpKhdAom2t/UjBVZh3l7DATU48rE4E/9pzLu8Urg9AVkfRHhLQi+6JZv7ZXDyEcUPtl6jENYZhT1CS2lPsDQ35Ap4BUgWVV8AOOacLVsomHRtS8Q1MtKbNvR+7tJeUEqRxCE7RNXM+FXMDPtwtn5DeKgdDF1HJh95G2Pw4m2W0ZtV1h6NtTuMZMgJtQSqF2IDwaPghLCqdznecLtwuUrVZxjxpuxX2vdYlmtLi2SPf1coKhRHYb4AMj8NNKmBZs9hgPOJxkvQxu8sh7XFbKSQ0gY"
]
}
]
Add the following guards section to zilla.json to define the JWT guard specifying the issuer and audience as well as using the keys previously downloaded from Auth0.
zilla.json (jwt guard)
Modify the http_server0 binding to add the authorization option to receive JWT credentials via either the authorization header or the access_token query parameter.
zilla.json (http authorization)
This allows the Zilla engine to validate the API caller's JWT access token at the http_server0 binding so that routes further along in the pipeline can verify the caller has the required roles.
Let's guard our GET Tasks API to require the read:tasks role.
Modify the sse_kafka_proxy0 binding to add the guarded section to the /tasks route, requiring read:tasks role.
zilla.json (guarded sse)
Let's also guard our POST, PUT and DELETE Tasks APIs to require the write:tasks role.
Modify the http_kafka_proxy0 binding to add the guarded section to the POST, PUT and DELETE routes, requiring write:tasks role.
zilla.json (guarded http)
Now run the command below to update the zilla service and force a restart.
docker service update --force \
$(docker stack services example -q -f name=example_zilla)
Let's verify the Tasks API using curl as shown below.
curl -v http://localhost:8080/tasks
> GET /tasks HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.79.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Length: 0
< Access-Control-Allow-Origin: *
<
As you can see, the GET /tasks API is now secured against unauthorized access, without leaking any information about failed security checks.

Step 2: Test Drive!

Open the browser and enter http://localhost:8080/ to see the secured Todo Application.
Initially you will see an error message caused by attempting to list the current tasks as an unauthorized user without the read:tasks role.
Click the Login button and follow the flow to become an authorized user, then you will see your profile picture in the upper right corner in place of the login button.
For the purposes of this guide, all authorized users are implicitly granted both read:tasks and write:tasks roles for the Tasks API at http://localhost:8080/tasks.
The Todo Application now behaves as expected, with authorized-only access to the Tasks API.