Skip to content

assembleinc/cognito-auth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

82 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cognito::Auth

Allows one to quickly boot up a rails application using Amazon Cognito as a login authentication service Converts AWS struct objects returned by the aws-sdk into Active Model objects

Installation

Add this line to your application's Gemfile:

gem 'cognito-auth', git: 'https://github.com/assemble-inc/cognito-auth.git', tag: '0.6.0'

Mount the gem in your project routes:

mount Cognito::Auth::Engine, at: "auth"

In any controllers that require the user to be logged in add:

before_action :validate!

Now if the user is not logged in they will be redirected to /auth/login

In your initializers add the code:

Cognito::Auth.configure do |config|
  config.user_pool_id = 'user pool id'
  config.client_id = 'client id'
  config.user_pool_region = 'user pool region'
end

Cognito::Auth.module_eval do
  def self.verify(payload)
    # any custom validations you want to apply to the user
  end
end

Aws.config.update({
   credentials: Aws::Credentials.new('key id', 'secret access key')
})

Other configuration parameters include:

  • token_refresh_rate: how often the access token will refresh, defaults to 1 hour
  • auto_verify_email: if set to true newly created users will have their email already verified
  • auto_verify_phonenumber: if set to true newly created users will have their phone number already verified
  • default_log_in: default log in flow used by authenticate method, defaults to USER_PASSWORD_AUTH
  • mail_from: the email address that invites to your application will use
  • mail_subject: subject for invitation emails

Edit the Rails credentials to store user_pool_id, client_id and user_pool_region example: env EDITOR="nano" rails credentials:edit

To set up the mailer either set config.action_mailer.default_url_options { host: your-base-url } in config/environments or overwrite views/cognito/auth/application_mailer/invite_email.html.erb

Cognito::Auth Module core methods

  • User Methods

    • authenticate(auth_parameters)

      • auth_parameters: { flow: The authentication flow method accepts USER_SRP_AUTH, REFRESH_TOKEN_AUTH, REFRESH_TOKEN, CUSTOM_AUTH, ADMIN_NO_SRP_AUTH, USER_PASSWORD_AUTH validation_data: custom data sent to the authentication lambda function used for any CUSTOM_AUTH Any remaining parameters required for the particular authentication flow. }

      • calls https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Client.html#admin_initiate_auth-instance_method

      • If authentication responds with a challenge it will store the challenge_name and session_token in cookies.

      • Otherwise it will store access_token, refresh_token, id_token, and token_expires in cookies and checks the id_token payload for the correct issuer and audience.

      • Then calls verify which can be overwritten to verify the token against the payload

      • If verification fails it will clear all cookies related to cognito auth

    • respond_to_auth_challenge(challenge_responses)

    • log_in(username, password, validation_data:{})

      • calls authenticate with the 'USER_PASSWORD_AUTH' flow
    • replace_temporary_password(username, new_pass)

      • calls respond to auth challenge with specific username and password
      • to be used against the NEW_PASSWORD_REQUIRED challenge
    • log_out

      • sets clears current_user, deletes all Cognito related tokens
    • send_verification_code(attribute)

    • verify_attribute(attribute, confirmation_code)

      • verifies the given attribute for the current user with the confirmation code emailed to the user
    • change_password(prevpass, newpass)

      • changes the current user's password
    • forgot_password(username)

      • sends a forgot_password email with a password recovery code to the given user.
      • username can be any verified username_alias attributes such as username, email or phone number
    • recover_password(username, confirmation_code, password)

      • changes the user's password based off the confirmation code emailed to them
    • current user

      • if logged in it will return the current user model otherwise returns nil
    • validate!

      • checks if access tokens are still valid and non expired
      • calls the verify function on the id_token payload for custom verifications
    • verify(payload)

      • by default always returns true
      • overwrite this method for custom validations against the id token payload every time validate is called
  • client methods

  • general methods

    • version

      • returns cognito auth version
    • configuration

      • holds all project configuration data
    • session

      • allows access to session variables set during authentication
    • errors

      • ActiveModel::Errors used in the session controller

Routes

root to: 'session#new'
get  '/login', to: "session#new"
post '/login', to: "session#create"
get  '/new-password-required', to: "session#edit_password"
post '/new-password-required', to: "session#update_password"
delete '/logout', to: "session#destroy"


get  '/forgot-password', to: "password#new"
post '/forgot-password', to: "password#create"
get  '/recover-password', to: "password#edit"
post '/recover-password', to: "password#update"

Models

  • User Instance Methods

    • Includes Active Model

    • save

      • saves the user and creates them if they don't exist
    • groups(limit: nil, page: nil)

      • gets the user's groups given a limit and page
      • cognito only allows max limit of 60 and pagination with next_tokens so to work around this:
        • if limit is set over 60 multiple queries will execute to get the result
        • if page is set a set of queries will execute to find the first group, and then a set of queries will execute to return the groups
        • if neither are set it will just return the first 60 results with one query
    • delete

      • deletes the user from cognito entirely and whipes the data in the user object
    • disable

      • disables the user in cognito so they are not allowed to log in or perform any actions
    • enable

      • reenable's the user
    • reset_password

      • resets the users password sending them an email with a confirmation code, same as using forgot password on them
    • global_log_out

      • invalidates all of the users access, id and refresh tokens
    • reload!

      • pulls the user data from cognito
    • update(params)

      • sets the user attributes to those defined in params
    • reset

      • if the user_status is FORCE_CHANGE_PASSWORD it will delete the user and create a new user instance with all the same groups and attributes, which will send them a new temporary password
    • change_password(password:"", proposed_password:"", confirm_password:"", confirm_password_required: true)

      • only valid to call on current user
      • sets their password to proposed password if password is correct
      • requires either confirm_password_required to be false or confirm_password to equal proposed password
      • stores any errors in Cognito::Auth.errors
    • full_name

      • returns first_name last_name
    • human_name

      • returns one of full_name, first_name or email if defined prioritizing in that order
    • ==(other)

      • checks if other has the same username as the user
  • User Class Methods

    • find(username)

      • returns the user model relating to the specific username
      • valid usernames are whichever username_aliases are set up for your project, one of: email, phone_number or username
      • if no user is found a Aws::CognitoIdentityProvider::Errors::UserNotFoundException is thrown
    • all(limit:nil, page: nil, filter: nil)

    • new(attributes)

      • creates a new User object with attributes
    • init_model(attributes)

      • initializes a User object from a attributes and flags it as not a new object
    • get_user_data(username)

      • returns a hash of attributes for a user with a specific username
      • returns nil if user doesn't exist
    • get_current_user_data

      • returns a hash of attributes for the current user
      • returns nil if current user doesn't exist
    • user_exists?(username)

      • probabaly inefficient to use this method but queries for user data and returns true if the user exists and false given a cognito error
  • Group Instance Methods

    • Includes Active Model

    • save

      • saves the group, creating a new one if it doesn't exist in cognito
    • delete

      • deletes the group from cognito
    • add_user(user)

      • user can be a username, user object or username alias such as email or phone_number
      • adds the user to the group
    • remove_user(user)

      • user can be a username, user object or username alias such as email or phone_number
      • removes the user to the group
    • invite_user(email)

      • sends the user a group_invite_email defined in Cognito::Auth::ApplicationMailer
      • adds the user to the group
      • if the user doesn't exist it will create them and add them to the group
    • resend_invite(email, reset:false)

      • if reset is set to true and the user hasn't set their password yet, then it will delete the user, and create a new user instance with the same attributes and send them an email with a new temporary_password
      • otherwise it sends the user a group_invite_email defined in Cognito::Auth::ApplicationMailer
      • adds the user to the group
      • if the user doesn't exist it will create them and add them to the group
    • create_and_add_user(email)

      • creates a new user with the specified email and adds them to the group
    • users(limit: nil, page: nil)

      • lists the users in the group
      • limit defaults to 60
      • does a query to collect every 60 results
      • does a query for every page of 60 results till it retrieves the correct page
    • rollback!

      • restores_attributes to state that you fetched them at
    • reload!

      • pulls group data in from cognito
    • ==(other)

      • checks for equality based off group name
  • Group Class Methods

    • find(group_name)

      • gets a group by group name
    • all(limit: nil, page: nil)

      • lists all groups in the user pool
      • limit defaults to 60
      • does a query to collect every 60 results
      • does a query for every page of 60 results till it retrieves the correct page
    • get_group_data(group_name)

      • returns a hash of group data for a group
    • new(attributes)

      • creates a new group instance from attributes
    • init_model(attributes)

      • creates a new group instance from attributes and flags them as not a new record

Locales

Flash notices are created with i18n keys. Any errors will have the key cognito-auth.{Error Name in camel case}. A full list of Cognito errors can be found here: https://docs.aws.amazon.com/sdkforruby/api/Aws/CognitoIdentityProvider/Errors.html

  • Specifically these ones show up in the login flow:

    • cognito-auth.not_authorized_exception (Cognito Authentication failed)
    • cognito-auth.user_not_found_exception (User is not part of the user pool)
    • cognito-auth.code_mismatch_exception (Password recovery code does not match the one sent in the email)
    • cognito-auth.invalid_password_exception (New password doesn't fit the password criteria)
  • Gem specific errors:

    • cognito-auth.no_user_error (for when someone tries to access the application while not logged in)
    • cognito-auth.not_authorized_error (for when someone tries to log in and is not part of the correct cognito groups to access the application)
  • Authorization challenges will have the key cognito-auth.{challenge name downcased}

    • cognito-auth.new_password_required (Warning that shows on the new password required page)
  • Additional locales that you need to set are:

    • cognito-auth.password_changed (When a password is changed through the forgot password flow)
    • cognito-auth.email_not_found (When forgot password is attempted but the specified email is not part of the userpool)
    • cognito-auth.recovery_code_sent (When an email is sent through the forgot password flow)
    • cognito-auth.new_temporary_password_sent (When a user goes through the forgot password flow but hasn't set their password yet and a new temporary password is sent)

External Data Source

If you would like to pull user data form another place other than cognito just extend the user model with forwardable

Example: in models/cognito/auth/user.rb to pull from a Mongoid document called UserProperties that stores user's username and a boolean called admin

module Cognito
  module Auth
    class User
      include Cognito::Auth::Concerns::User
      extend Forwardable

      attr_accessor :user_properties
      def_delegators :user_properties, :admin, :admin=

      def user_properties
        @user_properties ||= ::UserProperties.find_by(username: username)
      rescue
        ::UserProperties.new(username: username)
      end

      def save
        super
        user_properties.save
      end
    end
  end
end

Overwrite

  • Controllers

    • create a new file controllers/cognito/auth/{controller_to_overwite}.rb
    module Cognito
      module Auth
        class {ControllerToOverwrite} < ApplicationController
          include Cognito::Auth::Concerns::{ControllerToOverwrite}
          def {method_to_extend}
            super()
            # your code
          end
  • Models

    • create a new file models/cognito/auth/{model_to_overwrite}.rb
    module Cognito
      module Auth
        class {ModelToOverwrite} < ApplicationController
          include Cognito::Auth::Concerns::{ModelToOverwrite}
          attribute {new_field}, type: {new_type}, default: {new_default}
    
          def {method_to_extend}
            super()
            # your code
          end
    • custom attributes from aws will be accessed as :"custom:{new_field}"
    • in order to clean this up you can add the line alias_attribute :"custom:{new_field}" :{new_field}
    • since Cognito only stores strings and integers for custom attributes there is a custom attribute type included:
      • cognito_bool: converts a string stored in aws into a boolean and then back into a string in aws
  • Views

    • create a new file views/cognito/auth/{view_to_overwrite}.rb
    • to include a copy of the old view use render partial: {view_to_overwrite}
  • Assets

    • overwrite views/layouts/cognito/auth/application.html
    • include in the head: stylesheet_link_tag 'cognito/auth/application', media: "all" javascript_include_tag 'cognito/auth/application'
    • add a way to handle flash notices to your layout file
    • add your own stylesheets and javascript files

Contributing

Contribution directions go here.

License

The gem is available as open source under the terms of the MIT License.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •