In this article, we’re going to replace the Cognito Service, I choose the Kratos and Oathkeeper from Ory as alternative.
The main functionalities of Congito here, is offering a way to sign in, sign up with or without SSO, email verification and use Sesssion Web Token for Frontend authorization.
Here is the full architecture of authentication and authorization
Don’t worry if didnt understand the architecture, we will dig deeper in the next headlines
Kratos is an identity and user management service, we will use it to authenticate user in, verify the email and generate Session Web token to pass it to authorization service later.
One feature that others will consider it a downside is Kratos doesn’t come with an UI, but Ory have created a separate repo. The repo has a dockerfile to build the image, it stay to us to create a helm chart.
The installation of kratos is handled by a helm chart, but we need to change few values first:
Kratos needs a database to store its state, so we supply a connection string of our private database, I created the user kratos earlier.
For sending emails, a separate service is created alongside the kratos api, to get it working we add the SMTP credentials we have, I use mailgun as it provides an easy interface and integration mechanism with python, the from_address property here is the address that’s will appear in your inbox when you receive an email.
Flows property defines each stage like login, registration, verification and errors, because we’re using kratos UI all those pages will be under the root url like registration will be under: /registration.
We said before we need to send a verification email, and prevent unverified users from login by using this hook, so we modify the login to adjust the use case
We will not enable ingress, because kratos-ui requires both services to be deployed under the same host, but unfortunately kratos-api helm chart doesn’t enable this, that’s why we will offload this to kratos-ui chart.
The domain we’re going to host the kratos-ui in is kratos.enkinineveh.space and the kratos api under /app path, then we pass environment variable to specify kratos-app url, CSRF_COOKIE_NAME and two random secret values
and here it goes, let’s head into registration to create a user, then if we try to login, it’ll prevent us, so we must verify the user first using the verification form.
Next thing is deploying oathkeeper and sharing this session to authenticate the user.
Oathkeeper has two components API and Proxy, we will pick the proxy service to live in front of the exam-gen and exam-taking UI.
The Oathkeeper Proxy service has an access list to decide which request should be allowed or deny it. For example we can enable a cookie_session on url exam-generate-frontend that reference exam-generate internal upstream service
oathkeeper:# -- The ORY Oathkeeper configuration. For a full list of available settings, check:# https://github.com/ory/oathkeeper/blob/master/docs/config.yamlaccessRules:| [{"id": "exam-generation-rule","upstream": {"url": "http://exam-generation-frontend-charts:8501"},"match": {"url": "https://exam-generate-frontend.enkinineveh.space/<.*>","methods": ["GET","POST","OPTIONS","PUT","PATCH"]},"authenticators": [{"handler": "cookie_session"}],"authorizer": {"handler": "allow"},"mutators": [{"handler": "header","config": {"headers": {"X-USER-EMAIL": "{{ print .Extra.identity }}"}}}],"errors":[{"handler":"redirect"}]},
also we saw in previous article that the front-take exam needs the authenticated user’s email.
From the oathkeeper documentation we can leverage that with the “header” mutator, but I tried it many times and couldn’t get it to work.
If you have any idea how to make it work, I will be pleased to talk.
But you may ask yourself if oathkeeper and kratos are two separate services , how can oathkeeper verify the kratos cookie_session ?
Well kratos API provides the URL: /sessions/whoami to verify cookies, so when adding the cookie_session authenticator, we pass that url with few extra parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
authenticators:cookie_session:enabled:trueconfig:check_session_url:http://kratos-public.exam/sessions/whoami# this reference kratos internal urlforward_http_headers:- Cookie- X-USER-EMAILpreserve_path:trueextra_from:"@this"# kratos will be configured to put the subject from the IdP heresubject_from:"identity.traits.email"only:- ory_kratos_session
and one last thing in the configuration side is telling oathkeeper to redirect unauthorised requests to kratos login page and the return_to_query_param property is for redirecting user back to UI after successfully login
Proxy service will be deployed in front of the UI apps, so we update the ingress of the both frontend apps to redirect traffic to oathkeeper-proxy dns and othkeeper proxy will decide based on the access rules if the request should be allowed or not.
We nearly implement all the functionalities Cognito provides in this architecture but there is one missing feature, which is calling a PostSignUp function after registration to subscribe the educator to an Amazon Simple Notification Service , but in our case, we call PostSignUp to add a educator email in subscribers table so then the email-fn retrieve the email to send notifications to.
We start by creating the function, it should serve two main roles: intercepting the event and persisting the email inside a dynamodb table.
The handler function will get the email from event['traits']['email'] as this the format kratos webhook use, then we created a mongo client and inserted the email into MONGO_TABLE_NAME .
Wrap it inside FastAPI, build the image and push it into the registry.
Then we reference the image and add the environment variables to values.yaml as always:
1
2
3
4
5
6
7
8
9
10
11
image:repository:gitea.enkinineveh.space/gitea_admin/post-signup-fnpullPolicy:IfNotPresent# Overrides the image tag whose default is the chart appVersion.tag:"v1"env:normal:MONGO_URI:"mongodb://databaseAdmin:sHWKYbXRalmNExTMiYr@my-cluster-name-rs0.mongo.svc.cluster.local/admin?replicaSet=rs0&ssl=false"MONGO_TABLE_NAME:"subscribers"
After deploying the chart, we retrive the service URL:
1
2
3
4
kubectl get kservice -n exam post-signup-fn-charts
NAME URL LATESTCREATED LATESTREADY READY REASON
post-signup-fn-charts http://post-signup-fn-charts.exam.svc.cluster.local post-signup-fn-charts-00001 post-signup-fn-charts-00001 True
The other part of the puzzle is configuring kratos webhook to send educator only email to this functions, but how shall we identity educator emails from the student ones ?.
The answer to this question is adding another choice field contains user types, either: educator or student.
To achieve this, we will leverage the “custom identity schema” kratos provides, we already implemented the concept in previous step but we didn’t talk about it.
Identity schema is the form you’re seeing when you access the registration page, by default it comes with just email, password, fullanme , but we can customize it, to do that we change identity.schemas property. one side note is the identity schema use json-schema to create and validate forms
We update the release and test the registration process for an educator account. if the registration ran successfully we will see a document has been created on subscribers table.
Can you guess the last missing element of the puzzle ? It’s the emailing part, Sending an email when the exam is generated or the student has answered the questions. the knative-sevice will get triggered whenever a message reaches the email-topic we created on the previous steps.
the main.py in the knative service will be composed of 2 functions:
get_subscribers: to retrieve emails from the subscribers table.
send_email: will decode the event message and send it to the list of emails we have
We clearly done setting up everything, I will test the whole process from registring user and receiving verification code, generating an exam and receiving the email when it’s ready, answering the questions and getting the scoreboard mail.