Kulcare Docs
DeveloperAuthentication Documentation04 featuresAbha_phr

PHR Login Implementation Plan

Executive Summary

This document outlines the implementation plan for PHR (Personal Health Record) login functionality following the existing ABDM module conventions. The implementation follows a stateless architecture without database session storage, maintaining consistency with the current ABDM implementation patterns. The system supports multiple login methods including Mobile, Email, ABHA Address, and ABHA Number with both Aadhaar and ABHA OTP verification.

Key Architecture Decision

No Database Session Storage: Based on analysis of the existing ABDM system, we will NOT create database tables for session management. All session state (transaction IDs, tokens) will be managed by the frontend client, following the current stateless API pattern.

High-Level PHR Login Flow

Login Methods Supported

  1. Mobile Number Login - OTP-based authentication via registered mobile
  2. Email Login - OTP-based authentication via registered email
  3. ABHA Address Login - Using ABHA address with:
    • Mobile OTP
    • Email OTP
    • Password authentication
  4. ABHA Number Login - Using ABHA number with:
    • Aadhaar OTP
    • ABHA OTP
  5. Aadhaar Direct Login - Using Aadhaar number with OTP

Authentication Flow Steps

  1. Request OTP → Generate OTP for chosen authentication method, return txnId to frontend
  2. Verify OTP → Validate OTP using txnId, receive and return T-token to frontend
  3. Verify User → Use T-token to verify user, return X-token and R-token to frontend
  4. Session Management → Frontend stores and manages all tokens and session state

Implementation Architecture

Stateless Design Principles

  • No Server-Side Sessions: All session data managed by frontend
  • Token Pass-Through: Backend acts as a secure proxy to ABDM APIs
  • Request Logging Only: Use existing AbdmRequest model for audit logging
  • Direct Response Forwarding: Return ABDM API responses directly to frontend

Service Layer (Stateless Implementation)

1. Mobile Login - Request OTP

# parts/abdm/services/abdm/phr/login/mobile/request_otp.rb
module Abdm
  module Phr
    module Login
      module Mobile
        class RequestOtp
          def self.perform(mobile, user_id)
            token = Abdm::FetchGatewayTokenV3.perform
            encrypted_mobile = Abdm::Abha::EncryptPhrData.perform(mobile)
            
            url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/mobileOrEmailOtp/init"
            headers = {
              'Content-Type' => 'application/json',
              'Authorization' => "Bearer #{token}",
              'REQUEST-ID' => SecureRandom.uuid,
              'TIMESTAMP' => Time.now.utc.iso8601(3)
            }
            
            body = {
              value: encrypted_mobile,
              purpose: 'CM_ACCESS',
              authMode: 'MOBILE_OTP',
              requester: {
                type: 'PHR',
                id: 'IN0410000183'
              }
            }
            
            begin
              result = HTTParty.post(url, body: body.to_json, headers: headers)
            rescue StandardError => e
              error = e.to_s
            end
            
            # Log Request
            abdm_request = AbdmRequest.create!(
              url: url,
              payload: body,
              requested_by_id: user_id,
              requested_by_type: 'User'
            )
            
            # Update with response
            if result.present? && result.success?
              abdm_request.update!(
                response: result.parsed_response,
                status_code: result.code,
                success: true
              )
            else
              abdm_request.update!(
                response: error || result&.parsed_response,
                status_code: result&.code,
                success: false
              )
            end
            
            result
          end
        end
      end
    end
  end
end

2. Mobile Login - Verify OTP

# parts/abdm/services/abdm/phr/login/mobile/verify_otp.rb
module Abdm
  module Phr
    module Login
      module Mobile
        class VerifyOtp
          def self.perform(otp, txn_id, user_id)
            token = Abdm::FetchGatewayTokenV3.perform
            encrypted_otp = Abdm::Abha::EncryptPhrData.perform(otp)
            
            url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/mobileOrEmailOtp/preVerify"
            headers = {
              'Content-Type' => 'application/json',
              'Authorization' => "Bearer #{token}",
              'REQUEST-ID' => SecureRandom.uuid,
              'TIMESTAMP' => Time.now.utc.iso8601(3)
            }
            
            body = {
              transactionId: txn_id,
              authCode: encrypted_otp
            }
            
            begin
              result = HTTParty.post(url, body: body.to_json, headers: headers)
            rescue StandardError => e
              error = e.to_s
            end
            
            # Log Request
            abdm_request = AbdmRequest.create!(
              url: url,
              payload: body,
              requested_by_id: user_id,
              requested_by_type: 'User'
            )
            
            # Update with response
            if result.present? && result.success?
              abdm_request.update!(
                response: result.parsed_response,
                status_code: result.code,
                success: true
              )
            else
              abdm_request.update!(
                response: error || result&.parsed_response,
                status_code: result&.code,
                success: false
              )
            end
            
            result
          end
        end
      end
    end
  end
end

3. Email Login Services

# parts/abdm/services/abdm/phr/login/email/request_otp.rb
module Abdm
  module Phr
    module Login
      module Email
        class RequestOtp
          def self.perform(email, user_id)
            token = Abdm::FetchGatewayTokenV3.perform
            encrypted_email = Abdm::Abha::EncryptPhrData.perform(email)
            
            url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/mobileOrEmailOtp/init"
            headers = {
              'Content-Type' => 'application/json',
              'Authorization' => "Bearer #{token}",
              'REQUEST-ID' => SecureRandom.uuid,
              'TIMESTAMP' => Time.now.utc.iso8601(3)
            }
            
            body = {
              value: encrypted_email,
              purpose: 'CM_ACCESS',
              authMode: 'EMAIL_OTP',
              requester: {
                type: 'PHR',
                id: 'IN0410000183'
              }
            }
            
            # Similar request handling as Mobile
            # ... (same pattern as mobile)
            
            result
          end
        end
      end
    end
  end
end

4. ABHA Address Login Services

# parts/abdm/services/abdm/phr/login/abha_address/request_mobile_otp.rb
module Abdm
  module Phr
    module Login
      module AbhaAddress
        class RequestMobileOtp
          def self.perform(abha_address, user_id)
            token = Abdm::FetchGatewayTokenV3.perform
            encrypted_abha = Abdm::Abha::EncryptPhrData.perform(abha_address)
            
            url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/mobileOrEmailOtp/init"
            headers = {
              'Content-Type' => 'application/json',
              'Authorization' => "Bearer #{token}",
              'REQUEST-ID' => SecureRandom.uuid,
              'TIMESTAMP' => Time.now.utc.iso8601(3)
            }
            
            body = {
              value: encrypted_abha,
              purpose: 'CM_ACCESS',
              authMode: 'MOBILE_OTP',
              requester: {
                type: 'PHR',
                id: 'IN0410000183'
              }
            }
            
            # Standard request handling...
            result
          end
        end
        
        # parts/abdm/services/abdm/phr/login/abha_address/verify_password.rb
        class VerifyPassword
          def self.perform(abha_address, password, user_id)
            token = Abdm::FetchGatewayTokenV3.perform
            encrypted_abha = Abdm::Abha::EncryptPhrData.perform(abha_address)
            encrypted_password = Abdm::Abha::EncryptPhrData.perform(password)
            
            url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/abhaAddress/authPassword"
            
            body = {
              abhaAddress: encrypted_abha,
              password: encrypted_password
            }
            
            # Standard request handling...
            result
          end
        end
      end
    end
  end
end

5. ABHA Number Login Services

# parts/abdm/services/abdm/phr/login/abha_number/request_aadhaar_otp.rb
module Abdm
  module Phr
    module Login
      module AbhaNumber
        class RequestAadhaarOtp
          def self.perform(abha_number, user_id)
            token = Abdm::FetchGatewayTokenV3.perform
            encrypted_number = Abdm::Abha::EncryptPhrData.perform(abha_number)
            
            url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/aadhaar/generateOtp"
            
            body = {
              aadhaar: encrypted_number,
              purpose: 'CM_ACCESS'
            }
            
            # Standard request handling...
            result
          end
        end
        
        # parts/abdm/services/abdm/phr/login/abha_number/request_abha_otp.rb
        class RequestAbhaOtp
          def self.perform(abha_number, user_id)
            # Similar to above but different endpoint
            url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/abha/generateOtp"
            # ... implementation
          end
        end
      end
    end
  end
end

6. User Verification Service (Final Step)

# parts/abdm/services/abdm/phr/login/verify_user.rb
module Abdm
  module Phr
    module Login
      class VerifyUser
        def self.perform(transaction_id, t_token, user_id)
          token = Abdm::FetchGatewayTokenV3.perform
          
          url = "#{Rails.application.credentials.abdm_phr_host}/v3/phr/login/userAuthorizedToken"
          headers = {
            'Content-Type' => 'application/json',
            'Authorization' => "Bearer #{token}",
            'X-Token' => "Bearer #{t_token}",
            'REQUEST-ID' => SecureRandom.uuid,
            'TIMESTAMP' => Time.now.utc.iso8601(3)
          }
          
          body = {
            transactionId: transaction_id,
            patientId: nil,
            purpose: 'LINK'
          }
          
          begin
            result = HTTParty.post(url, body: body.to_json, headers: headers)
          rescue StandardError => e
            error = e.to_s
          end
          
          # Log Request
          abdm_request = AbdmRequest.create!(
            url: url,
            payload: body,
            requested_by_id: user_id,
            requested_by_type: 'User'
          )
          
          # Update with response
          if result.present? && result.success?
            abdm_request.update!(
              response: result.parsed_response,
              status_code: result.code,
              success: true
            )
          else
            abdm_request.update!(
              response: error || result&.parsed_response,
              status_code: result&.code,
              success: false
            )
          end
          
          result
        end
      end
    end
  end
end

Interactors (Business Logic - Stateless)

1. Mobile Login Send OTP Interactor

# parts/abdm/interactors/abdm/phr/login/mobile/send_otp.rb
module Abdm
  module Phr
    module Login
      module Mobile
        class SendOtp < BaseInteractor
          def call
            require_context(:mobile)
            
            validate_mobile
            
            result = Abdm::Phr::Login::Mobile::RequestOtp.perform(
              context.mobile,
              context.current_user&.id
            )
            
            if result&.success?
              context.records = result.parsed_response
              context.message = 'OTP sent successfully to mobile'
            else
              fail_with_errors!(result&.parsed_response || { error: 'Failed to send OTP' })
            end
          end
          
          private
          
          def validate_mobile
            fail_with_error!('mobile', 'is required') if context.mobile.blank?
            fail_with_error!('mobile', 'is invalid') unless valid_mobile?(context.mobile)
          end
          
          def valid_mobile?(mobile)
            mobile.match?(/\A[6-9]\d{9}\z/)
          end
        end
      end
    end
  end
end

2. Mobile Login Verify OTP Interactor

# parts/abdm/interactors/abdm/phr/login/mobile/verify_otp.rb
module Abdm
  module Phr
    module Login
      module Mobile
        class VerifyOtp < BaseInteractor
          def call
            require_context(:otp)
            require_context(:transaction_id)
            
            result = Abdm::Phr::Login::Mobile::VerifyOtp.perform(
              context.otp,
              context.transaction_id,
              context.current_user&.id
            )
            
            if result&.success?
              context.records = result.parsed_response
              context.message = 'OTP verified successfully'
            else
              fail_with_errors!(result&.parsed_response || { error: 'Failed to verify OTP' })
            end
          end
        end
      end
    end
  end
end

3. Email Login Interactors

# parts/abdm/interactors/abdm/phr/login/email/send_otp.rb
module Abdm
  module Phr
    module Login
      module Email
        class SendOtp < BaseInteractor
          def call
            require_context(:email)
            
            validate_email
            
            result = Abdm::Phr::Login::Email::RequestOtp.perform(
              context.email,
              context.current_user&.id
            )
            
            if result&.success?
              context.records = result.parsed_response
              context.message = 'OTP sent successfully to email'
            else
              fail_with_errors!(result&.parsed_response || { error: 'Failed to send OTP' })
            end
          end
          
          private
          
          def validate_email
            fail_with_error!('email', 'is required') if context.email.blank?
            fail_with_error!('email', 'is invalid') unless valid_email?(context.email)
          end
          
          def valid_email?(email)
            email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
          end
        end
      end
    end
  end
end

# parts/abdm/interactors/abdm/phr/login/email/verify_otp.rb
# Similar structure to Mobile::VerifyOtp

4. ABHA Address Login Interactors

# parts/abdm/interactors/abdm/phr/login/abha_address/send_mobile_otp.rb
module Abdm
  module Phr
    module Login
      module AbhaAddress
        class SendMobileOtp < BaseInteractor
          def call
            require_context(:abha_address)
            
            validate_abha_address
            
            result = Abdm::Phr::Login::AbhaAddress::RequestMobileOtp.perform(
              context.abha_address,
              context.current_user&.id
            )
            
            if result&.success?
              context.records = result.parsed_response
              context.message = 'OTP sent successfully'
            else
              fail_with_errors!(result&.parsed_response || { error: 'Failed to send OTP' })
            end
          end
          
          private
          
          def validate_abha_address
            fail_with_error!('abha_address', 'is required') if context.abha_address.blank?
            fail_with_error!('abha_address', 'is invalid') unless valid_abha_address?(context.abha_address)
          end
          
          def valid_abha_address?(address)
            address.match?(/^[a-zA-Z0-9._]+@[a-zA-Z]+$/)
          end
        end
      end
    end
  end
end

# parts/abdm/interactors/abdm/phr/login/abha_address/send_email_otp.rb
# parts/abdm/interactors/abdm/phr/login/abha_address/verify_mobile_otp.rb
# parts/abdm/interactors/abdm/phr/login/abha_address/verify_email_otp.rb
# parts/abdm/interactors/abdm/phr/login/abha_address/verify_password.rb

5. ABHA Number Login Interactors

# parts/abdm/interactors/abdm/phr/login/abha_number/send_aadhaar_otp.rb
module Abdm
  module Phr
    module Login
      module AbhaNumber
        class SendAadhaarOtp < BaseInteractor
          def call
            require_context(:abha_number)
            
            validate_abha_number
            
            result = Abdm::Phr::Login::AbhaNumber::RequestAadhaarOtp.perform(
              context.abha_number,
              context.current_user&.id
            )
            
            if result&.success?
              context.records = result.parsed_response
              context.message = 'Aadhaar OTP sent successfully'
            else
              fail_with_errors!(result&.parsed_response || { error: 'Failed to send OTP' })
            end
          end
          
          private
          
          def validate_abha_number
            fail_with_error!('abha_number', 'is required') if context.abha_number.blank?
            fail_with_error!('abha_number', 'is invalid') unless valid_abha_number?(context.abha_number)
          end
          
          def valid_abha_number?(number)
            number.match?(/^\d{2}-\d{4}-\d{4}-\d{4}$/)
          end
        end
      end
    end
  end
end

# parts/abdm/interactors/abdm/phr/login/abha_number/send_abha_otp.rb
# parts/abdm/interactors/abdm/phr/login/abha_number/verify_aadhaar_otp.rb
# parts/abdm/interactors/abdm/phr/login/abha_number/verify_abha_otp.rb

6. Aadhaar Login Interactors

# parts/abdm/interactors/abdm/phr/login/aadhaar/send_otp.rb
# parts/abdm/interactors/abdm/phr/login/aadhaar/verify_otp.rb

7. Verify User Interactor (Common)

# parts/abdm/interactors/abdm/phr/login/verify_user.rb
module Abdm
  module Phr
    module Login
      class VerifyUser < BaseInteractor
        def call
          require_context(:transaction_id)
          require_context(:t_token)
          
          result = Abdm::Phr::Login::VerifyUser.perform(
            context.transaction_id,
            context.t_token,
            context.current_user&.id
          )
          
          if result&.success?
            # Optionally update clinic patient if linked
            update_clinic_patient(result.parsed_response) if context.clinic_patient_id
            
            context.records = result.parsed_response
            context.message = 'User verified and logged in successfully'
          else
            fail_with_errors!(result&.parsed_response || { error: 'Failed to verify user' })
          end
        end
        
        private
        
        def update_clinic_patient(response_data)
          clinic_patient = ClinicPatient.find_by(id: context.clinic_patient_id)
          return unless clinic_patient
          
          clinic_patient.update!(
            phr_linked: true,
            phr_linked_at: Time.current,
            abha_address: response_data['phrAddress'],
            abha_number: response_data['ABHANumber']
          )
        end
      end
    end
  end
end

Controllers

PHR Login Controller (Stateless - Complete)

# parts/abdm/controllers/abdm/phr/login_controller.rb
module Abdm
  module Phr
    class LoginController < ApiController
      before_action :policy_check
      
      # Mobile Login Endpoints
      # POST /abdm/phr/login/mobile/send_otp
      def mobile_send_otp
        result = Login::Mobile::SendOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/mobile/verify_otp
      def mobile_verify_otp
        result = Login::Mobile::VerifyOtp.call(params)
        render_result(result)
      end
      
      # Email Login Endpoints
      # POST /abdm/phr/login/email/send_otp
      def email_send_otp
        result = Login::Email::SendOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/email/verify_otp
      def email_verify_otp
        result = Login::Email::VerifyOtp.call(params)
        render_result(result)
      end
      
      # ABHA Address Login Endpoints
      # POST /abdm/phr/login/abha_address/send_mobile_otp
      def abha_address_send_mobile_otp
        result = Login::AbhaAddress::SendMobileOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/abha_address/verify_mobile_otp
      def abha_address_verify_mobile_otp
        result = Login::AbhaAddress::VerifyMobileOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/abha_address/send_email_otp
      def abha_address_send_email_otp
        result = Login::AbhaAddress::SendEmailOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/abha_address/verify_email_otp
      def abha_address_verify_email_otp
        result = Login::AbhaAddress::VerifyEmailOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/abha_address/verify_password
      def abha_address_verify_password
        result = Login::AbhaAddress::VerifyPassword.call(params)
        render_result(result)
      end
      
      # ABHA Number Login Endpoints
      # POST /abdm/phr/login/abha_number/send_aadhaar_otp
      def abha_number_send_aadhaar_otp
        result = Login::AbhaNumber::SendAadhaarOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/abha_number/verify_aadhaar_otp
      def abha_number_verify_aadhaar_otp
        result = Login::AbhaNumber::VerifyAadhaarOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/abha_number/send_abha_otp
      def abha_number_send_abha_otp
        result = Login::AbhaNumber::SendAbhaOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/abha_number/verify_abha_otp
      def abha_number_verify_abha_otp
        result = Login::AbhaNumber::VerifyAbhaOtp.call(params)
        render_result(result)
      end
      
      # Aadhaar Direct Login Endpoints
      # POST /abdm/phr/login/aadhaar/send_otp
      def aadhaar_send_otp
        result = Login::Aadhaar::SendOtp.call(params)
        render_result(result)
      end
      
      # POST /abdm/phr/login/aadhaar/verify_otp
      def aadhaar_verify_otp
        result = Login::Aadhaar::VerifyOtp.call(params)
        render_result(result)
      end
      
      # Common Endpoint
      # POST /abdm/phr/login/verify_user
      def verify_user
        result = Login::VerifyUser.call(params)
        render_result(result)
      end
      
      private
      
      def policy_check
        authorize params, policy_class: PhrLoginControllerPolicy
      end
    end
  end
end

Routes Configuration

# parts/abdm/routes.rb (Addition to existing routes)
Rails.application.routes.draw do
  defaults format: :json do
    namespace :abdm do
      namespace :phr do
        namespace :login do
          # Mobile login
          namespace :mobile do
            post :send_otp
            post :verify_otp
          end
          
          # Email login
          namespace :email do
            post :send_otp
            post :verify_otp
          end
          
          # ABHA Address login
          namespace :abha_address do
            post :send_mobile_otp
            post :verify_mobile_otp
            post :send_email_otp
            post :verify_email_otp
            post :verify_password
          end
          
          # ABHA Number login
          namespace :abha_number do
            post :send_aadhaar_otp
            post :verify_aadhaar_otp
            post :send_abha_otp
            post :verify_abha_otp
          end
          
          # Aadhaar login
          namespace :aadhaar do
            post :send_otp
            post :verify_otp
          end
          
          # Common endpoints
          post :verify_user
        end
      end
    end
  end
end

Authorization Policy

# parts/abdm/policies/abdm/phr/login_controller_policy.rb
module Abdm
  module Phr
    class LoginControllerPolicy < BasePolicy
      # Mobile login
      def mobile_send_otp?
        true # Public endpoint
      end
      
      def mobile_verify_otp?
        true # Public endpoint
      end
      
      # Email login
      def email_send_otp?
        true # Public endpoint
      end
      
      def email_verify_otp?
        true # Public endpoint
      end
      
      # ABHA Address login
      def abha_address_send_mobile_otp?
        true # Public endpoint
      end
      
      def abha_address_verify_mobile_otp?
        true # Public endpoint
      end
      
      def abha_address_send_email_otp?
        true # Public endpoint
      end
      
      def abha_address_verify_email_otp?
        true # Public endpoint
      end
      
      def abha_address_verify_password?
        true # Public endpoint
      end
      
      # ABHA Number login
      def abha_number_send_aadhaar_otp?
        true # Public endpoint
      end
      
      def abha_number_verify_aadhaar_otp?
        true # Public endpoint
      end
      
      def abha_number_send_abha_otp?
        true # Public endpoint
      end
      
      def abha_number_verify_abha_otp?
        true # Public endpoint
      end
      
      # Aadhaar login
      def aadhaar_send_otp?
        true # Public endpoint
      end
      
      def aadhaar_verify_otp?
        true # Public endpoint
      end
      
      # Common
      def verify_user?
        true # Public endpoint
      end
    end
  end
end

API Request/Response Examples (Stateless)

1. Mobile Login - Send OTP

Request:

POST /abdm/phr/login/mobile/send_otp
{
  "mobile": "9876543210",
  "clinic_patient_id": 123 // Optional
}

Response:

{
  "success": true,
  "message": "OTP sent successfully to mobile",
  "data": {
    "transactionId": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"
  }
}

2. Mobile Login - Verify OTP

Request:

POST /abdm/phr/login/mobile/verify_otp
{
  "otp": "123456",
  "transaction_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"
}

Response:

{
  "success": true,
  "message": "OTP verified successfully",
  "data": {
    "authToken": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "abhaAddress": ["john.doe@abdm", "johndoe@sbx"]
  }
}

3. Verify User

Request:

POST /abdm/phr/login/verify_user
{
  "transaction_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
  "t_token": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "clinic_patient_id": 123 // Optional
}

Response:

{
  "success": true,
  "message": "User verified and logged in successfully",
  "data": {
    "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 1800,
    "phrAddress": "john.doe@abdm",
    "ABHANumber": "91-1234-5678-9012"
  }
}

Error Handling

Standard Error Response Format

{
  "success": false,
  "errors": {
    "code": "PHR_INVALID_OTP",
    "message": "Invalid OTP provided"
  }
}

Common Error Scenarios

  • Invalid OTP format or expired OTP
  • Invalid transaction ID
  • Token expired or invalid
  • ABDM API connectivity issues
  • Invalid mobile number format

Key Considerations (Stateless Architecture)

1. Frontend Responsibilities

  • Store and manage transaction IDs between API calls
  • Manage T-tokens and final access tokens
  • Handle token expiry and refresh logic
  • Maintain user session state

2. Security Considerations

  • All sensitive data encrypted using EncryptPhrData service
  • No session data stored in backend database
  • API request/response logging via AbdmRequest model
  • Rate limiting should be implemented at API gateway level

3. Token Flow

  • Transaction ID: Returned from send OTP, used in verify OTP
  • T-Token: Returned from verify OTP, used in verify user
  • Access Token: Returned from verify user, used for PHR API calls
  • Refresh Token: Used to refresh expired access tokens

4. Multiple ABHA Addresses

  • After OTP verification, response may contain multiple ABHA addresses
  • Frontend must allow user to select one address
  • Selected address passed to verify user endpoint

Testing Strategy

1. Unit Tests

# spec/services/abdm/phr/login/mobile/request_otp_spec.rb
# spec/interactors/abdm/phr/login/mobile/send_otp_spec.rb

2. Integration Tests

# spec/requests/abdm/phr/login_flow_spec.rb

3. Mock ABDM Responses

  • Mock successful OTP generation responses
  • Mock OTP verification responses with T-token
  • Mock user verification responses with access tokens

Configuration Requirements

Add to Rails credentials:

abdm_phr_host: "https://dev.abdm.gov.in/api"

Implementation Summary

Stateless Architecture Benefits

  1. Simplicity: No database session management complexity
  2. Scalability: Stateless backend scales horizontally
  3. Consistency: Follows existing ABDM module patterns
  4. Security: No sensitive session data stored in database

Files to Create

1. Controller (1 file)

  • parts/abdm/controllers/abdm/phr/login_controller.rb

2. Interactors (Business Logic - 20 files)

  • Mobile: mobile/send_otp.rb, mobile/verify_otp.rb
  • Email: email/send_otp.rb, email/verify_otp.rb
  • ABHA Address:
    • abha_address/send_mobile_otp.rb
    • abha_address/verify_mobile_otp.rb
    • abha_address/send_email_otp.rb
    • abha_address/verify_email_otp.rb
    • abha_address/verify_password.rb
  • ABHA Number:
    • abha_number/send_aadhaar_otp.rb
    • abha_number/verify_aadhaar_otp.rb
    • abha_number/send_abha_otp.rb
    • abha_number/verify_abha_otp.rb
  • Aadhaar: aadhaar/send_otp.rb, aadhaar/verify_otp.rb
  • Common: verify_user.rb

3. Services (API Communication - 20 files)

Same structure as interactors, handling actual ABDM API calls

4. Policy (1 file)

  • parts/abdm/policies/abdm/phr/login_controller_policy.rb

5. Routes (Update 1 file)

  • Update existing parts/abdm/routes.rb

Total: ~42 files to implement

Key Implementation Notes

  • No database migrations required
  • Frontend manages all session state
  • Backend acts as secure proxy to ABDM APIs
  • Uses existing AbdmRequest model for logging
  • Uses existing EncryptPhrData service for encryption

This implementation maintains consistency with the existing ABDM codebase while providing PHR login functionality in a clean, stateless manner.

On this page