Follow part 2, which shows how you connect your React/Javascript front end to endpoints covered here.
First of all, log into your AWS account and navigate to Cognito: https://console.aws.amazon.com/cognito/home
You should be greeted with something similar to this:
For standard authentication, you will want to click ‘Manage User Pools’ and then ‘Create new user pool.
You will then want to fill in some basic details for your user pool:
I will click ‘Step through settings’ in this guide.
I will allow users to sign in using username or email, therefore I check the box for Also allow sign in with verified email address
.
For my app, I would like to have the following:
- username
- password
- phone number
- picture
- zone info
- website
I will not need some of these fields until later, but I may as well setup the congnito up to support them now.
The next few sections I will keep all as default, however I will need to create an app client which will be able to access this user pool
As you can see, I set the refresh token to max allowed. For the time being, my app will not have an automated strategy to fetch this refresh token, so I don’t want to risk putting a small value in here.
Note to disable the Generate client secret
. Perhaps you will have it working with the library but at the time of writing it did not seem to be fully supported by the libraries I was using. Here’s a sample StackOverflow answer which suggests to disable it.
After I complete some base functionality and have resources to automate this, I will update it to something smaller.
I will also enable all auth flows:
Once complete, you will be able to review your user pool, then you will be able to check your client details
Here you will need to take note of the client ID and client secret.
For Rails backend, we will use 2 gems to integrate with:
gem 'aws-sdk-cognitoidentityprovider'
You can find a little more detail about them on
https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Aws/CognitoIdentityProvider/Client.html
https://github.com/aws/aws-sdk-ruby,
I was looking to find a guide on using these and came across: https://medium.com/@oscarreciogonzalez/integrate-rails-and-aws-cognito-tutorial-21cc38084055
This documents a lot of what we’re doing here.
Do note that to use the Cognito, we will also need to add some permissions, so go to IAM dashboard in AWS and go to ‘Groups’
Here you will want to ‘Create New Group’ and you can call it ‘CognitoAccess’ like me.
I attached these policies which seemed to have done the trick:
Then just add your AWS user to this group and it should be setup for use.
Now for coding, I created similar code to one in the guide with some differences, first off I create the model to represent Cognito Client: app/models/Cognito/aws_cognito.rb
# frozen_string_literal: true
module Cognito
class AwsCognito
attr_reader :client
def initialize
@client = @client || Aws::CognitoIdentityProvider::Client.new(region: ENV["AWS_REGION"])
end
def fetch_user(token)
client.get_user({access_token: token})
end
def authenticate(user_params)
user_object = {}
user_object[:USERNAME] = user_params[:username] if user_params[:username]
user_object[:EMAIL] = user_params[:email] if user_params[:email]
user_object[:PASSWORD] = user_params[:password] if user_params[:password]
auth_object = {
user_pool_id: ENV['AWS_COGNITO_POOL_ID'],
client_id: ENV['AWS_COGNITO_APP_CLIENT_ID'],
auth_flow: 'ADMIN_NO_SRP_AUTH',
auth_parameters: user_object
}
client.admin_initiate_auth(auth_object)
end
def sign_out(access_token)
client.global_sign_out(access_token: access_token)
end
def register_user(user_params)
auth_object = {
client_id: ENV['AWS_COGNITO_APP_CLIENT_ID'],
username: user_params[:username] || "",
password: user_params[:password] || "",
user_attributes: [
{
name: "picture",
value: "",
},
{
name: "website",
value: "",
},
{
name: "zoneinfo",
value: "",
},
{
name: "phone_number",
value: "",
},
{
name: "email",
value: user_params[:email] || "",
},
],
}
client.sign_up(auth_object)
end
def update_user(update_attr)
client.update_user_attributes(update_attr)
end
end
end
I then create the auth controller: app/controllers/api/v1/auth_controller.rb
# frozen_string_literal: true
module Api
module V1
class AuthController < ActionController::API
def sign_in
begin
render json: aws_congito_client.authenticate(user_params).authentication_result.to_hash
rescue => e
render status: :bad_request, json: {error: e.to_s}
end
end
def fetch_current_user
begin
render json: aws_congito_client.fetch_user(request.headers['Authentication']).to_hash
rescue => e
render status: :unauthorized, json: {error: e.to_s}
end
end
def sign_out
render json: aws_congito_client.sign_out(request.headers['Authentication']).to_hash
end
def register
begin
render json: aws_congito_client.register_user(user_params).to_hash
rescue => e
render status: :bad_request, json: {error: e.to_s}
end
end
private
def aws_congito_client
@aws_congito_client ||= Cognito::AwsCognito.new
end
def user_params
params.require(:user).permit(:email, :username, :password)
end
end
end
end
and I register the routes:
namespace :api, defaults: { format: :json } do
namespace :v1 do
scope :public do
post 'sign_in', to: 'auth#sign_in'
post 'sign_out', to: 'auth#sign_out'
post 'register', to: 'auth#register'
get 'current-user', to: 'auth#fetch_current_user'
end
end
A lot of these are temporary methods, e.g. I will change ‘sign_out’ to a DELETE type request, but I can sort this a bit later as and when I am integrating with my front end app – for the time being I am just interested in ensuring I have authentication working as expected.
With PostMan, I am able to send a request to register and sign in, and it appears to be working as expected:
Do note that I had to manually enable the user in Cognito UI:
This was the email that I received for confirmation:
I haven’t configured automated confirmations yet. After confirming manually in UI, I am able send a log in request:
Having been able to sign in, I have a bearer token so I will test methods for authentication:
client = Aws::CognitoIdentityProvider::Client.new(region: ENV["AWS_REGION"])
> client.get_user({access_token: token})
=> #<struct Aws::CognitoIdentityProvider::Types::GetUserResponse username="yazoo", user_attributes=[#<struct Aws::CognitoIdentityProvider::Types::AttributeType name="sub", value="487da623-8e65-489d-934c-af2ef586069a">, #<struct Aws::CognitoIdentityProvider::Types::AttributeType name="email_verified", value="false">, #<struct Aws::CognitoIdentityProvider::Types::AttributeType name="phone_number_verified", value="false">, #<struct Aws::CognitoIdentityProvider::Types::AttributeType name="email", value="y.lazarev@hotmail.com">], mfa_options=nil, preferred_mfa_setting=nil, user_mfa_setting_list=nil>
this is good news, this way I can authenticate my action requests with Cognito by checking that the access_token that will be part of my header requests is valid and corresponds to the user.
I have this hooked up to a controller request so I can also test this with Postman:
For the next part, I will look to have my React front-end link to these endpoints and complete the register – sign in – sign out flow.
Note that the above is just a skeleton for your authentication methods. You will still need to secure your actions behind something like ‘get_user’. e.g. before_action
of a secure function, call get user
with the authentication header. If the user is not found with the bearer token, you will get unauthorised and it will throw, which is what you want.
Next chapter will show javascript and react methods you can use to connect to these endpoints.