We have setup our Kubernetes cluster; we have setup the dashboard on how to monitor them; we have implemented a few services;
What next?
We need to build an application that will use these services - Let us take the first step to build an application - Authentication and Authorisation - The 2A's
Most times, while building an Application we concentrate a lot on the features of the application that the 2A's are an after thought.
I have currently started out with a problem statement and working on an application; I have come to a point where I can build on the function and build a product out of it
However, I have taken a pause! I am focussing on the 2A's as they are very important; they constitute two of the most important things as we start building any product. If the security around the product is not good; in this day and age, it just will not fly
In olden days we used to have all these fancy ways on how do I build the database for user management and spend a lot of time on the hashing of the password - lot has changed. Thanks to AWS Cognito; this helps you weave both the aspects of Authentication and Authorisation into your application.
In this example I am illustrating using Python & Flask - you can use the same concepts along with the nuances of the language you chose to develop your application with.
As I wade through AWS Cognito I find a lot of information scattered all over the place. I am trying to collate all that I found in one single place.
Let us deal with Part 1 - Authentication and follow it up with Authorisation.
There are two components of AWS Cognito - User Pool & Identity Pool
User Pool deals with Authentication and Identity pools deals with Authorisation
Let us consider the following features as part of User Management;
- Allow the user to signup and gets a confirmation email
- The user forgets to confirm and is sent a new confirmation
- The user confirms his account
- User has forgotten the password and an email is sent to the user to reset the password
- User used the code provided to set a new password
- The user now uses the new password and performs a successful login to the system
Boto3 has the following functions to cater to all of the about with the client "cognito-idp"
- sign_up
- resend_verification
- confirm_sign_up
- forgot_password
- confirm_forgot_password
- initiate_auth
How does this work
The entire component is multi-layered. Following are the component layers
- User Pools
- App Client
- Create an App client
Before we dive deep into these functions - Let us create a user pool
The user pool has a set of standard attributes
given name | middle name | family name | name |
name | nick name | preferred username | |
address | birthdate | phone number | gender |
locale | picture | profile | zoneinfo |
updated at | website |
At the time of pool creation we can set any or all of these as required - you cannot change them afterwards
- We can also define custom fields as needed
- Specify password characterstics
- Specify if users can signup or added by an administrator
- Specify Multi-Factor Authentication
- Password recovery methods
- Customise email & SMS messages
- Define Tags
- Indicate remembering user devices
- Define workflow at various stages of creation of a user
There are a lot more you can do including federation of identities
Once you define the user pool define an App Client for this pool, the app client has options on how authentication can be performed by the app client
You will need the user pool id and the app client id within your code to perform the authentication, if you enable to generate client secret you will need this too
Let us now go back to the functionality we defined as part of authentication initially.
Following choices made for the example
- Required Attributes
- name
- phone number
- username
- secret generated for app client
Function to create security hash
def get_secret_hash(username):
msg = username + CLIENT_ID
dig = hmac.new(str(CLIENT_SECRET).encode('utf-8'),
msg = str(msg).encode('utf-8'), digestmod=hashlib.sha256).digest()
securityHash = base64.b64encode(dig).decode()
return securityHash
The above function is called by all the boto3 api's when the App Client - generate secret is chosen, we get the has with the username - in our examples we will use email as the username
User Signup
response = cidpClient.sign_up(
ClientId=CLIENT_ID,
SecretHash=get_secret_hash(email),
Username=email,
Password=password,
UserAttributes=[
{
'Name': "name",
'Value': name
},
{
'Name': "email",
'Value': email
},
{
'Name': "phone_number",
'Value': phone
}
],
ValidationData=[
{
'Name': "email",
'Value': email
}
])
Pass in parameters which you have marked as required and also any user defined parameters
Verify Signup
response = cidpClient.confirm_sign_up(
ClientId=CLIENT_ID,
SecretHash=get_secret_hash(email),
Username=email,
ConfirmationCode=code,
ForceAliasCreation=False,
)
Verification Reminder
response = cidpClient.resend_confirmation_code(
ClientId=CLIENT_ID,
Username=email,
SecretHash=get_secret_hash(email),
)
Forgot Password
response = cidpClient.forgot_password(
ClientId=CLIENT_ID,
Username=email,
SecretHash=get_secret_hash(email),
)
Confirm Forgot Password
response = cidpClient.confirm_forgot_password(
ClientId=CLIENT_ID,
SecretHash=get_secret_hash(email),
Username=email,
ConfirmationCode=code,
Password=password,
)
Login
response = cidpClient.initiate_auth(
ClientId=CLIENT_ID,
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': email,
'SECRET_HASH': get_secret_hash(email),
'PASSWORD': password,
})
For each of these functions handle the corresponding exceptions to ensure that we handle all the exceptions.
Next section we will discussion authorisation and refreshing the tokens