Based on a customer conversation, I decided to look into the Dapr middleware components. More specifically, I wanted to understand how the OAuth 2.0 middleware works that enables the Authorization Code flow.
In the Authorization Code flow, an authorization code is a temporary code that a client obtains after being redirected to an authorization URL (https://login.microsoftonline.com/{tenant}/oauth2/authorize) where you provide your credentials interactively (not useful for service-service non-interactive scenarios). That code is then handed to your app which exchanges it for an access token. With the access token, the authenticated user can access your app.
Instead of coding this OAuth flow in your app, we will let the Dapr middleware handle all of that work. Our app can then pickup the token from an HTTP header. When there is a token, access to the app is granted. Otherwise, Dapr (well, the Dapr sidecar next to your app) redirects your client to the authorization server to get a code.
Let’s take a look how this all works with Azure Active Directory. Other authorization servers are supported as well: Facebook, GitHub, Google, and more.

Some experience with Kubernetes, deployments, ingresses, Ingress Controllers and Dapr is required.
If you think the explanation below can be improved, or I have made errors, do let me know. Let’s go…
Create an app registration
Using Azure AD means we need an app registration! Other platforms have similar requirements.
First, create an app registration following this quick start. In the first step, give the app a name and, for this demo, just select Accounts in this organizational directory only. The redirect URI will be configured later so just click Register.
After following the quick start, you should have:
- the client ID and client secret: will be used in the Dapr component
- the Azure AD tenant ID: used in the auth and token URLs in the Dapr component; Dapr needs to know where to redirect to and where to exchange the authorization code for an access token

There is no need for your app to know about these values. All work is done by Dapr and Dapr only!
We will come back to the app registration later to create a redirect URI.
Install an Ingress Controller
We will use an Ingress Controller to provide access to our app’s Dapr sidecar from the Internet, using HTTP.
In this example, we will install ingress-nginx. Use the following commands (requires Helm):
helm upgrade --install ingress-nginx ingress-nginx \ --repo https://kubernetes.github.io/ingress-nginx \ --namespace ingress-nginx --create-namespace
Although you will find articles about daprizing your Ingress Controller, we will not do that here. We will use the Ingress Controller simply as a way to provide HTTP access to the Dapr sidecar of our app. We do not want Dapr-to-Dapr gRPC traffic between the Ingress Controller and our app.
When ingress-nginx is installed, grab the public IP address of the service that it uses. Use kubectl get svc -n ingress-nginx
. I will use the IP address with nip.io to construct a host name like app.11.12.13.14.nip.io
. The nip.io service resolves such a host name to the IP address in the name automatically.
The host name will be used in the ingress and the Dapr component. In addition, use the host name to set the redirect URI of the app registration: https://app.11.12.13.14.nip.io
. For example:

Note that we are using https here. We will configure TLS on the ingress later.
Install Dapr
Install the Dapr CLI on your machine and run dapr init -k
. This requires a working Kubernetes context to install Dapr to your cluster. I am using a single-node AKS cluster in Azure.
Create the Dapr component and configuration
Below is the Dapr middleware component we need. The component is called myauth. Give it any name you want. The name will later be used in a Dapr configuration that is, in turn, used by the app.
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: myauth
spec:
type: middleware.http.oauth2
version: v1
metadata:
- name: clientId
value: "CLIENTID of your app reg"
- name: clientSecret
value: "CLIENTSECRET that you created on the app reg"
- name: authURL
value: "https://login.microsoftonline.com/TENANTID/oauth2/authorize"
- name: tokenURL
value: "https://login.microsoftonline.com/TENANTID/oauth2/token"
- name: redirectURL
value: "https://app.YOUR-IP.nip.io"
- name: authHeaderName
value: "authorization"
- name: forceHTTPS
value: "true"
scopes:
- super-api
Replace YOUR-IP with the public IP address of the Ingress Controller. Also replace the TENANTID.
With the information above, Dapr can exchange the authorization code for an access token. Note that the client secret is hard coded in the manifest. It is recommended to use a Kubernetes secret instead.
The component on its own is not enough. We need to create a Dapr configuration that references it:
piVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: auth
spec:
tracing:
samplingRate: "1"
httpPipeline:
handlers:
- name: myauth # reference the oauth component here
type: middleware.http.oauth2
Note that the configuration is called auth. Our app will need to use this configuration later, via an annotation on the Kubernetes pods.
Both manifests can be submitted to the cluster using kubectl apply -f. It is OK to use the default namespace for this demo. Keep the configuration and component in the same namespace as your app.
Deploy the app
The app we will deploy is super-api
, which has a /source
endpoint to dump all HTTP headers. When authentication is successful, the authorization header will be in the list.
Here is deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: super-api-deployment
labels:
app: super-api
spec:
replicas: 1
selector:
matchLabels:
app: super-api
template:
metadata:
labels:
app: super-api
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "super-api"
dapr.io/app-port: "8080"
dapr.io/config: "auth" # refer to Dapr config
dapr.io/sidecar-listen-addresses: "0.0.0.0" # important
spec:
securityContext:
runAsUser: 10000
runAsNonRoot: true
containers:
- name: super-api
image: ghcr.io/gbaeke/super:1.0.7
securityContext:
readOnlyRootFilesystem: true
capabilities:
drop:
- all
args: ["--port=8080"]
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: IPADDRESS
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: WELCOME
value: "Hello from the Super API on AKS!!! IP is: $(IPADDRESS)"
- name: LOG
value: "true"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "64Mi"
cpu: "50m"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 15
Note the annotations in the manifest above:
- dapr.io/enabled: injects the Dapr sidecar in the pods
- dapr.io/app-id: a Dapr app needs an id; a service will automatically be created with that
id
and-dapr
appended; in our case the name will besuper-api-dapr
; our ingress will forward traffic to this service - dapr.io/app-port: Dapr will need to call endpoints in our app (after authentication in this case) so it needs the port that our app container uses
- dapr.io/config: refers to the configuration we created above, which enables the http middleware defined by our OAuth component
- dapr.io/sidecar-listen-addresses: ⚠️ needs to be set to “0.0.0.0”; without this setting, we will not be able to send requests to the Dapr sidecar directly from the Ingress Controller
Submit the app manifest with kubectl apply -f
.
Check that the pod has two containers: the Dapr sidecar and your app container. Also check that there is a service called super-api-dapr
. There is no need to create your own service. Our ingress will forward traffic to this service.
Create an ingress
In the same namespace as the app (default), create an ingress. This requires the ingress-nginx Ingress Controller we installed earlier:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: super-api-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
tls:
- hosts:
- app.YOUR-IP.nip.io
secretName: tls-secret
rules:
- host: app.YOUR-IP.nip.io
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: super-api-dapr
port:
number: 80
Replace YOUR-IP with the public IP address of the Ingress Controller.
For this to work, you also need a secret with a certificate. Use the following commands:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=app.YOUR-IP.nip.io"
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
Replace YOUR-IP as above.
Testing the configuration
Let’s use the browser to connect to the /source
endpoint. You will need to use the Dapr invoke API because the request will be sent to the Dapr sidecar. You need to speak a language that Dapr understands! The sidecar will just call http://localhost:8080/source
and send back the response. It will only call the endpoint when authentication has succeeded, otherwise you will be redirected.
Use the following URL in the browser. It’s best to use an incognito session or private window.
https://app.20.103.17.249.nip.io/v1.0/invoke/super-api/method/source
Your browser will warn you of security risks because the certificate is not trusted. Proceed anyway! 😉
Note: we could use some URL rewriting on the ingress to avoid having to use /v1.0/invoke etc… You can also use different URL formats. See the docs.
You should get an authentication screen which indicates that the Dapr configuration is doing its thing:

After successful authentication, you should see the response from the /source
endpoint of super-api
:

The response contains an Authorization header. The header contains a JWT after the word Bearer. You can paste that JWT in https://jwt.io to see its content. We can only access the app with a valid token. That’s all we do in this case, ensuring only authenticated users can access our app.
Conclusion
In this article, we used Dapr to secure access to an app without having to modify the app itself. The source code of super-api
was not changed in any way to enable this functionality. Via a component and a configuration, we instructed our app’s Dapr sidecar to do all this work for us. App endpoints such as /source
are only called when there is a valid token. When there is such a token, it is saved in a header of your choice.
It is important to note that we have to send HTTP requests to our app’s sidecar for this to work. To enable this, we instructed the sidecar to listen on all IP addresses of the pod, not just 127.0.0.1. That allows us to send HTTP requests to the service that Dapr creates for the app. The ingress forwards requests to the Dapr service directly. That also means that you have to call your endpoint via the Dapr invoke API. I admit that can be confusing in the beginning. 😉
Note that, at the time of this writing (June 2022), the OAuth2 middleware in Dapr is in an alpha state.