Enforce authentication on 1st question

Hi all,

I want to enforce users to insert an ID (3 digit) in the first question. More specifically, the welcome message states “Please insert your ID in order to proceed. If you do not know the ID, type ‘help’”. The ID will be validated by a custom action by calling an API.

  • If the ID is valid, then they will be able to interract and get answers to their FAQs.
  • If the ID is not valid, then the bot will ask user to insert a valid ID.
  • If the user types ‘help’, an utter will state directions on where to find the ID.

Kindly note that I have a custom connector, and that the custom action with the API is ready.

What I do not know, is how to disable all other intents until the user inserts the valid ID, meaning if they ask anything else, then the bot must reply that they must login first…

Any idea how to proceed? More than happy to provide my code later on for further guidance, but for the moment I do not know how to start…

Thanks in advance

In the past, i need to build a solution like that I fouded two way to do that

1 - Front-end solution Build an login page and use rasa API to send auth

2 - Using conditional (i prefer this way) Create an slot like is_auth

domain.yml

slots:
   is_auth:
      type: bool
      initial_value: False
      influence_conversation: true

builded and action to Set this Slot true or false

and put the condition in the stories

stories.yml

- story: greet 
  steps:
  - intent: greet
  - slot_was_set:
    - is_auth: False
  - action: utter_please_make_login

- story: greet 
  steps:
  - intent: greet
  - slot_was_set:
    - is_auth: True
  - action: utter_greet

You can put condition in domain.yml too but i prefer in story

Reference:Conditional Response Variations: Technical Blog | The Rasa Blog | Rasa

Maybe you can try something like that. I don’t know its continue the story if auth == true

storie.yml

- story: greet 
  steps:
  - action: action_check_is_auth
  - action: utter_greet

action

from rasa_sdk.events import UserUtteranceReverted, SlotSet

is_auth = tracker.get_slot('is_auth')

if(!is_auth):
   return dispatcher.utter_message(response="utter_please_make_login")

return []

But yeah, this ways you need to put the verification in all stories. I didn’t find any rules to make that for like Any intent"

1 Like

Thanks for the response @itsjhonny. As I understand, this does not enforce the user to authenticate in the first question. Since the creation of a Front-end solution to make the login is not an option in my project, I’m guessing that there must be a way to enforce user to make the is_auth true prior asking anythine else.

You’re right though, with the method you proposed, it seems that I have to add this verificiation process in all stories, which is not feasible.

I also had a look at conversation_start: true (as below), which only applies for the beginning of the conversation, but again, it does not mandate user to login.

- rule: Action to verify login when the user starts a conversation
  conversation_start: true
  steps:
  - intent: greet
  - action: action_check_is_auth

Any other ideas from the community?

Yeah, this method I founded is very boring. I’m looking to the better method too

Maybe use active_loop with conversation_start: true ? Will teste some idea later and return :blush:

2 Likes

I think I founded an way using default-actions

I create a custom action to event listen This action check the slot is_auth If not authentication the action check if user can see this intent, defined in show_without_authentication In this case, the user don’t need to make login to see help_login intent

If not authenticate AND not can see the intent, the action send “utter_please_make_login”

action.py

from typing import Any, Text, Dict, List
from rasa_sdk import Action, Tracker
from rasa_sdk.events import SessionStarted,UserUtteranceReverted,ConversationPaused,ConversationResumed

class ActionActionListen(Action):
    def name(self) -> Text:
        return "action_listen"

    async def run(
      self, dispatcher, tracker: Tracker, domain: Dict[Text, Any]
    ) -> List[Dict[Text, Any]]:
        print('---- CUSTOM action_action_listen ----')
        is_auth = tracker.get_slot('is_auth')

        show_without_authentication = ['help_login']
        if not is_auth:
            _response = None
            current_intent = tracker.latest_message['intent'].get('name')
            if current_intent in show_without_authentication:
                _response = current_intent
            else:
                _response = "utter_please_make_login"
            dispatcher.utter_message(response=_response)
            return []

        return [dispatcher, tracker, domain]

The rules: I didn’t find any way to cancel rules action in python action. Need to use condition to check the slot

rules.yml

version: "2.0"

rules:

- rule: Say goodbye anytime the user says goodbye
  condition:
  - slot_was_set:
    - is_auth: True
  steps:
    - intent: goodbye
    - action: utter_goodbye

- rule: Say 'I am a bot' anytime the user challenges
  condition:
  - slot_was_set:
    - is_auth: True
  steps:
    - intent: bot_challenge
    - action: utter_iamabot

- rule: Say 'I am a bot' anytime the user challenges
  condition:
  - slot_was_set:
    - is_auth: False
  steps:
    - intent: help_login
    - action: utter_help_login

my domain.yml

version: "2.0"

intents:
  - greet
  - goodbye
  - affirm
  - deny
  - mood_great
  - mood_unhappy
  - bot_challenge
  - help_login
slots:
  is_auth:
    type: bool
    initial_value: False
    auto_fill: False
    influence_conversation: true

responses:
  utter_greet:
  - text: "Hey! How are you?"

  utter_cheer_up:
  - text: "Here is something to cheer you up:"
    image: "https://i.imgur.com/nGF1K8f.jpg"

  utter_did_that_help:
  - text: "Did that help you?"

  utter_happy:
  - text: "Great, carry on!"

  utter_goodbye:
  - text: "Bye"

  utter_please_make_login: 
  - text: "Please make login. type **help** for more informations"

  utter_iamabot:
  - text: "I am a bot, powered by Rasa."

  utter_help_login:
  - text: 'type login true to make the login'

actions:
  - action_listen
session_config:
  session_expiration_time: 60
  carry_over_slots_to_new_session: true

config.yml

# Configuration for Rasa NLU.
# https://rasa.com/docs/rasa/nlu/components/
language: en

pipeline:
policies:
  - name: RulePolicy

I zip my project to test and make this way better :smiley:

I’m using rasa 2.8.27

Use rasa train --force

rasa2-8.zip (10.5 KB)

References:

---- Edit ----- when i set Policies the solution stop to working

policies:
  - name: MemoizationPolicy
  - name: TEDPolicy
    max_history: 5
    epochs: 200
  - name: RulePolicy

Hi @v_kostis :smiley: In my testes, I think isn’t possible to make authentication system using custom actions

action_listen and action_session_start is cool but, don’t stop any menssagens

So, we have the way build using condition for every story and an front-end solution

But, I think found a nice way build an Custom Connectors btw, the official docs are outdated

My custom connector check slot is_auth if false and not allowed intent, the user text changed to ‘/please_make_login’

Ex:

  • user input text “hi”-> (intent greet, it not allowed for no-auth users) → original user text changed to /please_make_login
  • user input text “need help”-> (intent help_login, its allowed for no-auth users) → no changes

‘/auth_true’ call the action ‘action_set_auth_true’ and set slot ‘is_auth’ true Im insert some prints

custom_channel.py

import asyncio
import inspect
import aiohttp
import json
from sanic import Sanic, Blueprint, response
from sanic.request import Request
from sanic.response import HTTPResponse
from typing import Text, Dict, Any, Optional, Callable, Awaitable, NoReturn

import rasa.utils.endpoints


from rasa.core.channels.channel import (
    InputChannel,
    CollectingOutputChannel,
    UserMessage,
)

class MyIO(InputChannel):
    def name(name) -> Text:
        return "myio"


    async def teste(self,url,payload):
        async with aiohttp.ClientSession() as session:
            
            async with session.post(url,data=payload) as response:
                try:
                    return await response.json()
                except Exception as e:
                    
                    return await response.text()

    def blueprint(
        self, on_new_message: Callable[[UserMessage], Awaitable[None]]
    ) -> Blueprint:

        custom_webhook = Blueprint(
            "custom_webhook_{}".format(type(self).__name__),
            inspect.getmodule(self).__name__,
        )
    
        @custom_webhook.route("/", methods=["GET"])
        async def health(request: Request) -> HTTPResponse:
            return response.json({"status": "ok"})
        

        @custom_webhook.route("/webhook", methods=["POST"])
        async def receive(request: Request) -> HTTPResponse:
            sender_id = request.json.get("sender") # method to get sender_id 
            text = request.json.get("text") # method to fetch text
            input_channel = self.name() # method to fetch input channel
            metadata = self.get_metadata(request) # method to get metadata
            collector = CollectingOutputChannel()
            tracker = request.app.agent.tracker_store.get_or_create_tracker(sender_id)
            is_auth = tracker.get_slot('is_auth')


            if not is_auth: #change user text to 
                
                show_without_authentication = ['help_login','auth_true']
                payload = {"text": text}
                intent = await self.teste("http://localhost:5005/model/parse",json.dumps(payload))
                intent = intent["intent"]["name"]

                if intent not in show_without_authentication:
                   text = "/please_make_login" 

            await on_new_message(
                UserMessage(
                    text,
                    collector,
                    sender_id,
                    metadata,
                    input_channel=input_channel,
                )
            )
            return response.json(collector.messages)

        return custom_webhook

rasa2-8_auth_custom_channel.zip (16.6 KB)

Thanks @itsjhonny. I’ll look it straight away and revert. Clever idea to play around with the custom channel by the way. Looks promising!

Hi @itsjhonny,

I concluded that the first solution you proposed is the best option. Everything else is overkill for the moment.

More specifically, we created the below action:

class ActionCheckIsAuth(Action):
    def name(self) -> Text:
        return "action_check_is_auth"

    def run(self, dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
        if(tracker.get_slot('is_auth')!=True):
            dispatcher.utter_message(response="utter_cannot_login")
        return []

We enforced the said action to every rule…

- rule: demo rule
  steps:
  - intent: id_01
  - action: action_check_is_auth
  - action: utter_id_01

Finally, we created the condition under which the slot is_auth is considered True

# conditions where is_auth is False, meaning no agent has successfully logged in.
- rule: Cannot login
  condition:
  - slot_was_set:
    - is_auth: False
  steps:
  - intent: cannot_login
  - action: utter_cannot_login

- rule: Check_ID
  condition:
  - slot_was_set:
    - is_auth: False
  steps:
  - intent: provide_id
  - action: action_check_agent_id

In the above, action_check_agent_id is the custom action that validates the agent input via an API.

Of course, we created the respective slot

slots:
  is_auth:
    type: bool
    initial_value: true
    influence_conversation: true
    mappings:
    - type: custom

And that’s it. I suppose there could be a more effective solution, and there is a danger of forgetting to add the action: action_check_is_auth when a new rule is created, but at the moment it works like a charm.

Thanks for your support. Your 1st answer is marked as Solution.

1 Like