Repository: pablosnt/rekono (language: python)

Issue:
How to do Authentication(Either with Credentials/Session Cookie) using rekono
**What do you need?**
Is there way we can use credentials or session cookies to login and run all the Tools in Rekono to Test the Device Under Test

Below are some error seen due to lack of authentication:
`Tool: Log4j Scanner`
```
Traceback (most recent call last):
  File "/opt/log4j-scanner/log4-scanner/log4j-scan.py", line 395, in <module>
    main()
  File "/opt/log4j-scanner/log4-scanner/log4j-scan.py", line 367, in main
    dns_callback = Interactsh()
  File "/opt/log4j-scanner/log4-scanner/log4j-scan.py", line 224, in __init__
    self.register()
  File "/opt/log4j-scanner/log4-scanner/log4j-scan.py", line 232, in register
    res = self.session.post(
  File "/usr/local/lib/python3.10/dist-packages/requests/sessions.py", line 590, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/requests/sessions.py", line 542, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/lib/python3.10/dist-packages/requests/sessions.py", line 655, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/requests/adapters.py", line 504, in send
    raise ConnectTimeout(e, request=request)
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='interact.sh', port=443): Max retries exceeded with url: /register (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7f89bce376a0>, 'Connection to interact.sh timed out. (connect timeout=30)'))
```
`Tool: ZAP`
```
Found Java version 11.0.16
Available memory: 7954 MB
Using JVM args: -Xmx1988m
809 [main] INFO  org.parosproxy.paros.Constant - Copying default configuration to /home/rekono/.ZAP/config.xml
977 [main] INFO  org.parosproxy.paros.Constant - Creating directory /home/rekono/.ZAP/session
977 [main] INFO  org.parosproxy.paros.Constant - Creating directory /home/rekono/.ZAP/dirbuster
978 [main] INFO  org.parosproxy.paros.Constant - Creating directory /home/rekono/.ZAP/fuzzers
978 [main] INFO  org.parosproxy.paros.Constant - Creating directory /home/rekono/.ZAP/plugin
Accessing URL
Failed to attack the URL: received a 404 response code, expected 200.
Writing results to /rekono/reports/16aff3d3-9d45-4898-9a66-f1367ed8d402.xml
```

`Tool: CMSeek`

```
TERM environment variable not set.
Traceback (most recent call last):
  File "/usr/share/cmseek/cmseek.py", line 116, in <module>
    core.main_proc(target,cua)
  File "/usr/share/cmseek/cmseekdb/core.py", line 52, in main_proc
    follow_redir = input('[#] Set ' + cmseek.bold + cmseek.fgreen + init_source[3] + cmseek.cln + ' as target? (y/n): ')
EOFError: EOF when reading a line
```

`Tool: SearchSploit`
```
{
	"SEARCH": "TLS 1.2 > /rekono/reports/1d4712d1-a6c8-4073-8388-36c8fa55dcce.json",
	"DB_PATH_EXPLOIT": "/usr/share/exploitdb",
	"RESULTS_EXPLOIT": [	],
	"DB_PATH_SHELLCODE": "/usr/share/exploitdb",
	"RESULTS_SHELLCODE": [	]
}
```



Discussion / hints:
I'm not sure if I'm understanding you regarding to credentials or session cookies. For authentication in Rekono, you can use the session in the frontend or your API token to make requests directly to the API REST. You can get your API key in your profile after input your password. Moreover, to use the API REST, you can also use the [rekono-cli](https://github.com/pablosnt/rekono-cli) project.

About the tools errors, there aren't due to lack of authentication. The reason of these errors are very differents:

- **Log4j-scanner**: it's a known error in the log4j-scanner tool that I have reported in the following issues: https://github.com/cisagov/log4j-scanner/issues/55 and https://github.com/fullhunt/log4j-scan/issues/131

- **OWASP ZAP**: it isn't an error, it seems that the URL attacked by ZAP isn't available (returns an HTTP 404), so ZAP execution fails. To check if it is a Rekono error or not, can you check the exactly command that Rekono executes in the executions-workers log? Is the target URL correct?

- **CMSeeK**: very similar to the other errors. Personally, I never see it before, so can you check in the logs if the target URL is correct? If the URL is correct, it can be a CMSeeK error

- **SearchSploit**: I don't see any error in your output. The output of SearchSploit is in JSON format, the empty lists mean that no exploits found for TLS 1.2 technology

I don't know if I can help you in anything else, but in general, this errors seems to belong to the external hacking tools or due to the specific target that you are scanning
Sorry, I finally understand this issue after read again your description. No, at the moment is not possible to execute hacking tools using authentication against the target services. I know that it's a very interesting and needed feature, and I have included it to the backlog yet, even before make Rekono public. I will keep this issue open to keep it in mind :)
@pablosnt  ok I mainly wanted this is because my Server is configured with authentication, inorder for these tools to login to my Server and run scan/attack, somewhere there should be a way to configure either authentication(basic/form port/ntlm) or cookie based session ids.
Do we have any tweaks I can make in the code to get this working.... or any other workaround or I have to wait till you have the fix for it...
Yes, of course, it's a feature that Rekono should include but it can't be implemented yet. This development can need some time, because it can be complex, includes frontend and backend development and a lot of authentication methods should be considered.

As fast and very bad workaround, you can configure manually your tools in your database to use this authentication tokens or credentials, or you can create specific tool configurations to use these authentication. Of course, this workaround would apply the authentication configuration for all your targets and projects. I'm referring to this: https://github.com/pablosnt/rekono/blob/main/rekono/tools/fixtures/3_configurations.json#L248. As I said, it's a very bad workaround, but it could work for you while the feature is not released
@pablosnt  I am fine with bad workaround, all I need is to use those subset of tools in your main tool. Based on the code you referred above, are you saying to change the 3_configurations.json file in my local code outside docker container as below(in this case considering ZAP tool for now):
```
{
        "model": "tools.configuration",
        "pk": 25,
        "fields": {
            "tool": 8,
            "name": "Active scan",
            "arguments": "-cmd {url} -quickprogress -quickout {output} -cookie "csrftoken=csrftoken123; toolsessionid=toolsessionid123; sessionid=sessionid123"",
            "default": true
        }
    }
```
Once above code is added, I have to rebuild with no-cache and bring up the containers to make the above code reflected in the container. 
Is my understanding correct?
Yes, this file is the default data in the Rekono database. So, if you have a Rekono instance up, you also can change this value directly in the Configuration table of the Rekono database. If you change the value in the file, you need to re-deploy the full environment, because it's needed to initialize the database again
ok I see.. Also should I need to figure out what is the arguments I have to give as per the cli of the Tool or I can simply give like this:
`-cmd {url} -quickprogress -quickout {output} -cookie "csrftoken=csrftoken123; toolsessionid=toolsessionid123; sessionid=sessionid123"`
I did quick search on zap command line, I found below two links:
```
https://www.zaproxy.org/docs/desktop/cmdline/
https://www.zaproxy.org/docs/desktop/addons/quick-start/cmdline/
```
Need to research on -config option to pass cookie... Seems like for every tool I have to find the command line option and add it to 3_configurations.json file for "arguments".
Will try to add and see how it goes..

Thanks
I see following containers, in which container(name of the container would be helpful) I can directly change the file so that I need not rebuild the code. 
```
└─$ docker ps
CONTAINER ID   IMAGE                                                 COMMAND                  CREATED        STATUS                  PORTS                                                                      NAMES
040156d03b6b   rekono-nginx:latest                                   "/docker-entrypoint.…"   47 hours ago   Up 47 hours             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   nginx
691860d47957   rekono-kali:latest                                    "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-executions-worker-4
6abc5b916c4f   rekono-kali:latest                                    "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-executions-worker-2
2836be29f56d   rekono-backend:latest                                 "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-tasks-worker-1
bf30b8b18cd8   rekono-backend:latest                                 "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-findings-worker-1
88021a0e9bc1   rekono-backend:latest                                 "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-emails-worker-1
9d45ba201de5   rekono-kali:latest                                    "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-executions-worker-3
d3a070846685   rekono-kali:latest                                    "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-executions-worker-5
4c8f34537d20   rekono-backend:latest                                 "python manage.py ru…"   47 hours ago   Up 47 hours (healthy)   8000/tcp                                                                   backend
c832f2b8f23d   rekono-kali:latest                                    "python manage.py rq…"   47 hours ago   Up 47 hours                                                                                        rekono-executions-worker-1
b05591d663d4   rekono-frontend:latest                                "docker-entrypoint.s…"   47 hours ago   Up 47 hours (healthy)                                                                              frontend
bc0447c16b8f   postgres:14.2-alpine                                  "docker-entrypoint.s…"   47 hours ago   Up 47 hours                                                                                        postgres
04bcf7f4fe49   catatnight/postfix:latest                             "/bin/sh -c '/opt/in…"   47 hours ago   Up 47 hours             25/tcp                                                                     postfix
6ba59f2bfcb5   redis:6.2.6-alpine                                    "docker-entrypoint.s…"   47 hours ago   Up 47 hours                                                                                        redis
```
No, I think that I didn't explain myself. You have two options: edit the file in your system and build the environment again or edit the Rekono database directly. The Rekono database is the postgres container, where you can access the Configuration table in the "rekono" database using the credentials "postgres:postgres" (if you didn't change it)
Sure @pablosnt the table name in postgres would be tools_configuration and below is the commands of postgres which need to be run:
```
rekono=# update tools_configuration
set arguments = '-cmd {url} -config "csrftoken=abc" -quickprogress -quickout {output}'
where id = 25;
```
will use this as a workaround.

Thanks

Fix patch:
diff --git a/.secrets.baseline b/.secrets.baseline
index 0e505ee00..28c175863 100644
--- a/.secrets.baseline
+++ b/.secrets.baseline
@@ -407,6 +407,14 @@
         "line_number": 45,
         "is_secret": false
       },
+      {
+        "type": "Mailchimp Access Key",
+        "filename": "rekono/testing/data/reports/gitleaks/leaky-repo.json",
+        "hashed_secret": "4dcd64be183918e45527449e4945c9730f44fc86",
+        "is_verified": false,
+        "line_number": 62,
+        "is_secret": false
+      },
       {
         "type": "Secret Keyword",
         "filename": "rekono/testing/data/reports/gitleaks/leaky-repo.json",
@@ -776,7 +784,7 @@
         "filename": "rekono/testing/executions/test_base_tool.py",
         "hashed_secret": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
         "is_verified": false,
-        "line_number": 294,
+        "line_number": 306,
         "is_secret": false
       }
     ],
@@ -805,6 +813,14 @@
         "line_number": 34,
         "is_secret": false
       },
+      {
+        "type": "Mailchimp Access Key",
+        "filename": "rekono/testing/tools/test_gitleaks.py",
+        "hashed_secret": "4dcd64be183918e45527449e4945c9730f44fc86",
+        "is_verified": false,
+        "line_number": 39,
+        "is_secret": false
+      },
       {
         "type": "Secret Keyword",
         "filename": "rekono/testing/tools/test_gitleaks.py",
@@ -831,5 +847,5 @@
       }
     ]
   },
-  "generated_at": "2022-11-23T22:24:55Z"
+  "generated_at": "2022-12-16T17:35:49Z"
 }
diff --git a/rekono/api/serializers.py b/rekono/api/serializers.py
index 6e207372b..16b47337a 100644
--- a/rekono/api/serializers.py
+++ b/rekono/api/serializers.py
@@ -36,3 +36,30 @@ class RekonoTagSerializerField(TagListSerializerField):
     '''Internal serializer field for TagListSerializerField, including API documentation.'''
 
     pass
+
+
+@extend_schema_field(OpenApiTypes.STR)
+class ProtectedStringValueField(serializers.Field):
+    '''Serializer field to manage protected system values.'''
+
+    def to_representation(self, value: str) -> str:
+        '''Return text value to send to the client.
+
+        Args:
+            value (str): Internal text value
+
+        Returns:
+            str: Text value that contains multiple '*' characters
+        '''
+        return '*' * len(value)
+
+    def to_internal_value(self, value: str) -> str:
+        '''Return text value to be stored in database.
+
+        Args:
+            value (str): Text value provided by the client
+
+        Returns:
+            str: Text value to be stored. Save value than the provided one.
+        '''
+        return value
diff --git a/rekono/authentications/__init__.py b/rekono/authentications/__init__.py
new file mode 100644
index 000000000..e4ebe9578
--- /dev/null
+++ b/rekono/authentications/__init__.py
@@ -0,0 +1,1 @@
+'''Authentications.'''
diff --git a/rekono/authentications/admin.py b/rekono/authentications/admin.py
new file mode 100644
index 000000000..33f7c57ad
--- /dev/null
+++ b/rekono/authentications/admin.py
@@ -0,0 +1,7 @@
+from django.contrib import admin
+
+from authentications.models import Authentication
+
+# Register your models here.
+
+admin.site.register(Authentication)
diff --git a/rekono/authentications/apps.py b/rekono/authentications/apps.py
new file mode 100644
index 000000000..f57f3b4bb
--- /dev/null
+++ b/rekono/authentications/apps.py
@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+
+
+class AuthenticationConfig(AppConfig):
+    '''Authentication Django application.'''
+
+    name = 'authentications'
diff --git a/rekono/authentications/enums.py b/rekono/authentications/enums.py
new file mode 100644
index 000000000..9cb7b3ffc
--- /dev/null
+++ b/rekono/authentications/enums.py
@@ -0,0 +1,12 @@
+from django.db import models
+
+
+class AuthenticationType(models.TextChoices):
+    '''Supported authentication types.'''
+
+    BASIC = 'Basic'
+    BEARER = 'Bearer'
+    COOKIE = 'Cookie'
+    DIGEST = 'Digest'
+    JWT = 'JWT'
+    NTLM = 'NTLM'
diff --git a/rekono/authentications/filters.py b/rekono/authentications/filters.py
new file mode 100644
index 000000000..48f499af0
--- /dev/null
+++ b/rekono/authentications/filters.py
@@ -0,0 +1,26 @@
+from django_filters import rest_framework
+from django_filters.rest_framework.filters import OrderingFilter
+
+from authentications.models import Authentication
+
+
+class AuthenticationFilter(rest_framework.FilterSet):
+    '''FilterSet to filter and sort authentications entities.'''
+
+    o = OrderingFilter(fields=('target_port', 'name', 'type'))                  # Ordering fields
+
+    class Meta:
+        model = Authentication
+        fields = {                                                              # Filter fields
+            'target_port': ['exact'],
+            'target_port__port': ['exact'],
+            'target_port__target': ['exact'],
+            'target_port__target__project': ['exact'],
+            'target_port__target__project__name': ['exact', 'icontains'],
+            'target_port__target__project__owner': ['exact'],
+            'target_port__target__project__owner__username': ['exact', 'icontains'],
+            'target_port__target__target': ['exact', 'icontains'],
+            'target_port__target__type': ['exact'],
+            'name': ['exact', 'icontains'],
+            'type': ['exact']
+        }
diff --git a/rekono/authentications/migrations/0001_initial.py b/rekono/authentications/migrations/0001_initial.py
new file mode 100644
index 000000000..04f537eac
--- /dev/null
+++ b/rekono/authentications/migrations/0001_initial.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.16 on 2022-12-13 23:07
+
+from django.db import migrations, models
+import django.db.models.deletion
+import input_types.base
+import security.input_validation
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('targets', '0002_delete_targetendpoint'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Authentication',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.TextField(max_length=100, validators=[security.input_validation.validate_name])),
+                ('credential', models.TextField(max_length=500, validators=[security.input_validation.validate_credential])),
+                ('type', models.TextField(choices=[('Basic', 'Basic'), ('Bearer', 'Bearer'), ('Cookie', 'Cookie'), ('Digest', 'Digest'), ('JWT', 'Jwt'), ('NTLM', 'Ntlm')], max_length=8)),
+                ('target_port', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='authentication', to='targets.targetport')),
+            ],
+            bases=(models.Model, input_types.base.BaseInput),
+        ),
+    ]
diff --git a/rekono/authentications/migrations/__init__.py b/rekono/authentications/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/rekono/authentications/models.py b/rekono/authentications/models.py
new file mode 100644
index 000000000..36ad4ab2a
--- /dev/null
+++ b/rekono/authentications/models.py
@@ -0,0 +1,75 @@
+import base64
+from typing import Any, Dict
+
+from django.db import models
+from input_types.enums import InputKeyword
+from input_types.models import BaseInput
+from projects.models import Project
+from security.input_validation import validate_credential, validate_name
+from targets.models import TargetPort
+from tools.models import Input
+
+from authentications.enums import AuthenticationType
+
+# Create your models here.
+
+
+class Authentication(models.Model, BaseInput):
+    '''Authentication model.'''
+
+    # Related target port
+    target_port = models.OneToOneField(TargetPort, related_name='authentication', on_delete=models.CASCADE)
+    name = models.TextField(max_length=100, validators=[validate_name])         # Credential name
+    credential = models.TextField(max_length=500, validators=[validate_credential])     # Credential value
+    type = models.TextField(max_length=8, choices=AuthenticationType.choices)   # Authentication type
+
+    def filter(self, input: Input) -> bool:
+        '''Check if this instance is valid based on input filter.
+
+        Args:
+            input (Input): Tool input whose filter will be applied
+
+        Returns:
+            bool: Indicate if this instance match the input filter or not
+        '''
+        if input.filter and input.filter[0] == '!':                             # Negative filter
+            return self.type.lower() not in input.filter[1:].split(',')         # Check if filter doesn't match the type
+        # Check if filter matches the type
+        return not input.filter or self.type.lower() in input.filter.lower().split(',')
+
+    def parse(self, accumulated: Dict[str, Any] = {}) -> Dict[str, Any]:
+        '''Get useful information from this instance to be used in tool execution as argument.
+
+        Args:
+            accumulated (Dict[str, Any], optional): Information from other instances of the same type. Defaults to {}.
+
+        Returns:
+            Dict[str, Any]: Useful information for tool executions, including accumulated if setted
+        '''
+        output = self.target_port.parse()
+        credential = {
+            InputKeyword.USERNAME.name.lower(): self.name if self.type == AuthenticationType.BASIC else None,
+            InputKeyword.COOKIE_NAME.name.lower(): self.name if self.type == AuthenticationType.COOKIE else None,
+            InputKeyword.SECRET.name.lower(): self.credential,
+            InputKeyword.TOKEN.name.lower(): self.credential if self.type != AuthenticationType.BASIC else base64.b64encode(f'{self.name}:{self.credential}'.encode()).decode(),  # noqa: E501
+            InputKeyword.CREDENTIAL_TYPE.name.lower(): self.type,
+            InputKeyword.CREDENTIAL_TYPE_LOWER.name.lower(): self.type.lower(),
+        }
+        output.update(credential)
+        return output
+
+    def __str__(self) -> str:
+        '''Instance representation in text format.
+
+        Returns:
+            str: String value that identifies this instance
+        '''
+        return f'{self.target_port.__str__()} - {self.name}'
+
+    def get_project(self) -> Project:
+        '''Get the related project for the instance. This will be used for authorization purposes.
+
+        Returns:
+            Project: Related project entity
+        '''
+        return self.target_port.target.project
diff --git a/rekono/authentications/serializers.py b/rekono/authentications/serializers.py
new file mode 100644
index 000000000..a8de9e642
--- /dev/null
+++ b/rekono/authentications/serializers.py
@@ -0,0 +1,35 @@
+from typing import Any, Dict
+
+from api.serializers import ProtectedStringValueField
+from rest_framework import serializers
+from security.input_validation import validate_credential
+
+from authentications.models import Authentication
+
+
+class AuthenticationSerializer(serializers.ModelSerializer):
+    '''Serializer to manage authentications via API.'''
+
+    credential = ProtectedStringValueField(required=True, allow_null=False)     # Credential value in a protected way
+
+    class Meta:
+        '''Serializer metadata.'''
+
+        model = Authentication
+        fields = ('id', 'target_port', 'name', 'credential', 'type')            # Authentication fields exposed via API
+
+    def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]:
+        '''Validate the provided data before use it.
+
+        Args:
+            attrs (Dict[str, Any]): Provided data
+
+        Raises:
+            ValidationError: Raised if provided data is invalid
+
+        Returns:
+            Dict[str, Any]: Data after validation process
+        '''
+        attrs = super().validate(attrs)
+        validate_credential(attrs['credential'])
+        return attrs
diff --git a/rekono/authentications/urls.py b/rekono/authentications/urls.py
new file mode 100644
index 000000000..a33ad4ef0
--- /dev/null
+++ b/rekono/authentications/urls.py
@@ -0,0 +1,8 @@
+from rest_framework.routers import SimpleRouter
+
+from authentications.views import AuthenticationViewSet
+
+router = SimpleRouter()
+router.register('authentications', AuthenticationViewSet)
+
+urlpatterns = router.urls
diff --git a/rekono/authentications/views.py b/rekono/authentications/views.py
new file mode 100644
index 000000000..1c1f35859
--- /dev/null
+++ b/rekono/authentications/views.py
@@ -0,0 +1,31 @@
+from typing import Any, Dict
+
+from django.db.models import QuerySet
+from targets.views import TargetViewSet
+
+from authentications.filters import AuthenticationFilter
+from authentications.models import Authentication
+from authentications.serializers import AuthenticationSerializer
+
+# Create your views here.
+
+
+class AuthenticationViewSet(TargetViewSet):
+    '''Authentication ViewSet that includes: get, retrieve, create, and delete features.'''
+
+    queryset = Authentication.objects.all().order_by('-id')
+    serializer_class = AuthenticationSerializer
+    filterset_class = AuthenticationFilter
+    search_fields = ['name']
+    project_members_field = 'target_port__target__project__members'
+
+    def get_project_members(self, data: Dict[str, Any]) -> QuerySet:
+        '''Get project members from serializer validated data.
+
+        Args:
+            data (Dict[str, Any]): Validated data from serializer
+
+        Returns:
+            QuerySet: Project members related to the instance
+        '''
+        return data['target_port'].target.project.members.all()
diff --git a/rekono/frontend/src/backend/RekonoApi.vue b/rekono/frontend/src/backend/RekonoApi.vue
index d7acc68f7..3a0c1da7a 100644
--- a/rekono/frontend/src/backend/RekonoApi.vue
+++ b/rekono/frontend/src/backend/RekonoApi.vue
@@ -77,11 +77,13 @@ export default {
       ],
       cancellableStatuses: ['Requested', 'Running'],
       timeUnits: ['Weeks', 'Days', 'Hours', 'Minutes'],
+      credentialTypes: ['Basic', 'Bearer', 'Cookie', 'Digest', 'JWT', 'NTLM'],
       nameRegex: /^[\wÀ-ÿ\s.\-[\]()]{0,100}$/,
       textRegex: /^[\wÀ-ÿ\s.:,+\-'"?¿¡!#%$€[\]()]{0,300}$/,
-      cveRegex: /^CVE-[0-9]{4}-[0-9]{1,7}$/,
-      defectDojoKeyRegex: /^[0-9a-z]{40}$/,
-      telegramTokenRegex: /^[0-9]{10}:[\w-]{35}$/,
+      cveRegex: /^CVE-\d{4}-\d{1,7}$/,
+      defectDojoKeyRegex: /^[\da-z]{40}$/,
+      telegramTokenRegex: /^\d{10}:[\w-]{35}$/,
+      credentialRegex: /^[\d\w./\-=+,:<>¿?¡!#&$()[\]{}*]{1,500}$/,
       telegramBotName: null,
       defectDojoUrl: null,
       defectDojoEnabled: null
@@ -266,6 +268,9 @@ export default {
     validateTelegramToken (value) {
       return this.validate(value, this.telegramTokenRegex)
     },
+    validateCredential (value) {
+      return this.validate(value, this.credentialRegex)
+    },
     validateUrl (value) {
       try {
         new URL(value);
diff --git a/rekono/frontend/src/common/TargetPortDetail.vue b/rekono/frontend/src/common/TargetPortDetail.vue
new file mode 100644
index 000000000..7a1ffe23b
--- /dev/null
+++ b/rekono/frontend/src/common/TargetPortDetail.vue
@@ -0,0 +1,79 @@
+<template>
+  <div>
+    <authentication  v-if="name === 'authentication'" :targetPortId="targetPortId" @update="fetchData()"/>
+    <target-technology  v-if="name === 'technology'" :targetPortId="targetPortId" @update="fetchData()"/>
+    <target-vulnerability v-if="name === 'vulnerability'" :targetPortId="targetPortId" @update="fetchData()"/>
+    <b-table stripped head-variant="light" :fields="tableFields" :items="data" v-if="data.length > 0">
+      <template #cell(actions)="row">
+        <b-button variant="outline" @click="selectItem(row.item)" v-b-tooltip.hover title="Delete" v-b-modal="'delete-' + name + '-modal'">
+          <b-icon variant="danger" icon="trash-fill"/>
+        </b-button>
+      </template>
+    </b-table>
+    <deletion :id="'delete-' + name + '-modal'" :title="'Delete ' + name.charAt(0).toUpperCase() + name.slice(1)" @deletion="deleteItem" @clean="cleanSelection" v-if="selectedItem !== null">
+      <span><strong>{{ selectedItem[field] }}</strong> {{ name.toLowerCase() }}</span>
+    </deletion>
+    <pagination :page="page" :limit="limit" :limits="limits" :total="total" :name="endpoint.replace('target-', '')" @pagination="pagination"/>
+  </div>
+</template>
+
+<script>
+import RekonoApi from '@/backend/RekonoApi'
+import Deletion from '@/common/Deletion'
+import Pagination from '@/common/Pagination'
+import Authentication from '@/modals/Authentication'
+import TargetTechnology from '@/modals/TargetTechnology'
+import TargetVulnerability from '@/modals/TargetVulnerability'
+export default {
+  name: 'targetPortDetail',
+  mixins: [RekonoApi],
+  props: {
+    targetPortId: Number,
+    endpoint: String,
+    name: String,
+    field: String,
+    fields: Array
+  },
+  computed: {
+    tableFields () {
+      let all_fields = this.fields
+      all_fields.push({ key: 'actions' })
+      return all_fields
+    }
+  },
+  components: {
+    Deletion,
+    Pagination,
+    TargetVulnerability,
+    TargetTechnology,
+    Authentication
+  },
+  data () {
+    this.fetchData()
+    return {
+      limit: 10,
+      limits: [10, 25, 50, 100],
+      data: [],
+      selectedItem: null
+    }
+  },
+  methods: {
+    fetchData () {
+      return this.getOnePage(`/api/${this.endpoint}/?o=${this.field}`, { target_port: this.targetPortId })
+        .then(response => {
+          this.data = response.data.results
+          this.total = response.data.count
+        })
+    },
+    deleteItem () {
+      this.delete(`/api/${this.endpoint}/${this.selectedItem.id}/`).then(() => this.fetchData())
+    },
+    selectItem (item) {
+      this.selectedItem = item
+    },
+    cleanSelection () {
+      this.selectedItem = null
+    }
+  }
+}
+</script>
\ No newline at end of file
diff --git a/rekono/frontend/src/components/project/Targets.vue b/rekono/frontend/src/components/project/Targets.vue
index 9405a1d4c..765e25042 100644
--- a/rekono/frontend/src/components/project/Targets.vue
+++ b/rekono/frontend/src/components/project/Targets.vue
@@ -34,9 +34,8 @@
         <b-card>
           <b-table striped borderles head-variant="light" :fields="targetPortsFields" :items="row.item.target_ports">
             <template #cell(actions)="port">
-              <b-button @click="showTargetPort = port.item.id; selectTargetPort(row.item, port.item)" variant="outline" class="mr-2" v-b-tooltip.hover title="Details" v-b-modal.target-port-details>
-                <b-icon v-if="showTargetPort !== port.item.id" variant="dark" icon="eye-fill"/>
-                <b-icon v-if="showTargetPort === port.item.id" variant="dark" icon="eye-slash-fill"/>
+              <b-button @click="selectTargetPort(row.item, port.item)" variant="outline" class="mr-2" v-b-tooltip.hover title="Configuration" v-b-modal.target-port-details>
+                <b-icon variant="dark" icon="nut-fill"/>
               </b-button>
               <b-button variant="outline" @click="selectTargetPort(row.item, port.item)" v-b-tooltip.hover title="Delete Port" v-b-modal.delete-target-port-modal>
                 <b-icon variant="danger" icon="trash-fill"/>
@@ -55,7 +54,7 @@
     </deletion>
     <target id="add-target-modal" :projectId="$route.params.id" @confirm="confirm"/>
     <target-port v-if="selectedTarget !== null" id="add-target-port-modal" :targetId="selectedTarget.id" @confirm="confirm"/>
-    <target-port-details v-if="selectedTargetPort !== null" id="target-port-details" :targetPort="selectedTargetPort" @close="showTargetPort = null; fetchData()"/>
+    <target-port-details v-if="selectedTargetPort !== null" id="target-port-details" :targetPort="selectedTargetPort" @close="fetchData()"/>
     <task id="task-modal" :target="selectedTarget" :initialized="selectedTarget !== null" @clean="cleanSelection"/>
   </div>
 </template>
@@ -104,7 +103,6 @@ export default {
       selectedTarget: null,
       selectedTargetPort: null,
       showTargetId: null,
-      showTargetPort: null,
       filters: []
     }
   },
@@ -156,7 +154,6 @@ export default {
     cleanSelection () {
       this.selectedTarget = null
       this.selectedTargetPort = null
-      this.showTargetPort = null
     }
   }
 }
diff --git a/rekono/frontend/src/modals/Authentication.vue b/rekono/frontend/src/modals/Authentication.vue
new file mode 100644
index 000000000..1b48be1cd
--- /dev/null
+++ b/rekono/frontend/src/modals/Authentication.vue
@@ -0,0 +1,91 @@
+<template>
+  <b-row>
+    <b-col cols="1">
+      <b-input-group-append v-b-tooltip.hover title="The following tools accept credentials to perform authenticated analysis: Dirsearch (all authentication types), SMBMap (basic authentication), JoomScan (cookie authentication), Nikto (cookie and basic authentication) and OWASP ZAP (all authentication types)">
+        <b-button variant="outline">
+          <b-icon icon="info-circle-fill" variant="info"/>
+        </b-button>
+      </b-input-group-append>
+    </b-col>
+    <b-col cols="9">
+      <b-form>
+        <b-row>
+          <b-col cols="3">
+            <b-form-select v-model="newType" :options="credentialTypes">Type</b-form-select>
+          </b-col>
+          <b-col cols="4">
+            <b-form-group :invalid-feedback="invalidName">
+              <b-form-input type="text" v-model="newName" :placeholder="newType === 'Basic' ? 'Username' : newType === 'Cookie' ? 'Cookie name' : 'Credential name'" :state="nameState" autofocus/>
+            </b-form-group>
+          </b-col>
+          <b-col cols="5">
+            <b-form-group :invalid-feedback="invalidCredential">
+              <b-form-input type="password" v-model="newCredential" :placeholder="newType === 'Basic' ? 'Password' : newType === 'Cookie' ? 'Cookie value' : 'Credential value'" :state="credentialState" autofocus/>
+            </b-form-group>
+          </b-col>
+        </b-row>
+      </b-form>
+    </b-col>
+    <b-col cols="2">
+      <b-button variant="outline" @click="create" v-b-tooltip.hover title="Create">
+        <p class="h3"><b-icon variant="success" icon="plus-square-fill"/></p>
+      </b-button>
+    </b-col>
+  </b-row>
+</template>
+
+<script>
+import RekonoApi from '@/backend/RekonoApi';
+export default {
+  name: 'authenticationModal',
+  mixins: [RekonoApi],
+  props: {
+    targetPortId: Number,
+  },
+  data () {
+    return {
+      newType: 'Basic',
+      newName: null,
+      newCredential: null,
+      invalidName: 'Credential name is required',
+      invalidCredential: 'Credential value is required',
+      nameState: null,
+      credentialState: null
+    }
+  },
+  methods: {
+    check () {
+      this.nameState = null
+      this.credentialState = null
+      if (!this.validateName(this.newName)) {
+        this.techState = false
+        this.invalidName = this.newName && this.newName.length > 0 ? 'Invalid credential name' : 'Credential name is required'
+      }
+      if (!this.validateCredential(this.newCredential)) {
+        this.credentialState = false
+        this.invalidCredential = this.newCredential && this.newCredential.length > 0 ? 'Invalid credential value' : 'Credential value is required'
+      }
+      return this.nameState !== false && this.credentialState !== false
+    },
+    create () {
+      if (this.check()) {
+        this.post(
+          '/api/authentications/',
+          { target_port: this.targetPortId, name: this.newName, credential: this.newCredential, type: this.newType },
+          this.newName, 'New authentication created successfully'
+        )
+          .then(() => this.$emit('update'))
+        this.clean()
+      }
+    },
+    clean () {
+      this.newName = null
+      this.newCredential = null
+      this.invalidName = 'Credential name is required'
+      this.invalidCredential = 'Credential value is required'
+      this.nameState = null
+      this.credentialState = null
+    }
+  }
+}
+</script>
\ No newline at end of file
diff --git a/rekono/frontend/src/modals/TargetPortDetail.vue b/rekono/frontend/src/modals/TargetPortDetail.vue
deleted file mode 100644
index 389106956..000000000
--- a/rekono/frontend/src/modals/TargetPortDetail.vue
+++ /dev/null
@@ -1,162 +0,0 @@
-<template>
-  <div>
-    <b-row>
-      <b-col cols="10">
-        <b-form  v-if="name === 'vulnerability'">
-          <b-form-group :invalid-feedback="invalidCve">
-            <b-form-input type="text" v-model="newCve" placeholder="CVE" :state="cveState" autofocus/>
-          </b-form-group>
-        </b-form>
-        <b-form  v-if="name === 'technology'">
-          <b-row>
-            <b-col cols="6">
-              <b-form-group :invalid-feedback="invalidTech">
-                <b-form-input type="text" v-model="newTech" placeholder="Technology Name" :state="techState" autofocus/>
-              </b-form-group>
-            </b-col>
-            <b-col cols="6">
-              <b-form-group :invalid-feedback="invalidVersion">
-                <b-form-input type="text" v-model="newVersion" placeholder="Technology Version" :state="versionState" autofocus/>
-              </b-form-group>
-            </b-col>
-          </b-row>
-        </b-form>
-      </b-col>
-      <b-col cols="2">
-        <b-button variant="outline" @click="create" v-b-tooltip.hover title="Create">
-          <p class="h3"><b-icon variant="success" icon="plus-square-fill"/></p>
-        </b-button>
-      </b-col>
-    </b-row>
-    <b-table stripped head-variant="light" :fields="tableFields" :items="data" v-if="data.length > 0">
-      <template #cell(actions)="row">
-        <b-button variant="outline" @click="selectItem(row.item)" v-b-tooltip.hover title="Delete" v-b-modal="'delete-' + name + '-modal'">
-          <b-icon variant="danger" icon="trash-fill"/>
-        </b-button>
-      </template>
-    </b-table>
-    <deletion :id="'delete-' + name + '-modal'" :title="'Delete ' + name.charAt(0).toUpperCase() + name.slice(1)" @deletion="deleteItem" @clean="cleanSelection" v-if="selectedItem !== null">
-      <span><strong>{{ selectedItem[field] }}</strong> {{ name.toLowerCase() }}</span>
-    </deletion>
-    <pagination :page="page" :limit="limit" :limits="limits" :total="total" :name="endpoint" @pagination="pagination"/>
-  </div>
-</template>
-
-<script>
-import RekonoApi from '@/backend/RekonoApi'
-import Deletion from '@/common/Deletion'
-import Pagination from '@/common/Pagination'
-export default {
-  name: 'targetPortDetails',
-  mixins: [RekonoApi],
-  props: {
-    targetPortId: Number,
-    endpoint: String,
-    name: String,
-    field: String,
-    fields: Array
-  },
-  computed: {
-    tableFields () {
-      let all_fields = this.fields
-      all_fields.push({ key: 'actions' })
-      return all_fields
-    }
-  },
-  components: {
-    Deletion,
-    Pagination
-  },
-  data () {
-    this.fetchData()
-    return {
-      limit: 10,
-      limits: [10, 25, 50, 100],
-      data: [],
-      selectedItem: null,
-      newEndpoint: null,
-      invalidEndpoint: 'Endpoint is required',
-      endpointState: null,
-      newCve: null,
-      invalidCve: 'CVE is required',
-      cveState: null,
-      newTech: null,
-      newVersion: null,
-      invalidTech: 'Technology name is required',
-      invalidVersion: 'Technology version is required',
-      techState: null,
-      versionState: null
-    }
-  },
-  methods: {
-    fetchData () {
-      return this.getOnePage(`/api/target-${this.endpoint}/?o=${this.field}`, { target_port: this.targetPortId })
-        .then(response => {
-          this.data = response.data.results
-          this.total = response.data.count
-        })
-    },
-    check () {
-      this.endpointState = null
-      this.cveState = null
-      this.techState = null
-      this.versionState = null
-      if (this.name === 'vulnerability' && !this.validateCve(this.newCve)) {
-        this.cveState = false
-        this.invalidCve = this.newCve && this.newCve.length > 0 ? 'Invalid CVE' : 'CVE is required'
-        return false
-      } else if (this.name === 'technology') {
-        if (!this.validateName(this.newTech)) {
-          this.techState = false
-          this.invalidName = this.newTech && this.newTech.length > 0 ? 'Invalid technology name' : 'Technology name is required'
-        }
-        if (!this.validateName(this.newVersion)) {
-          this.versionState = false
-          this.invalidVersion = this.newVersion && this.newVersion.length > 0 ? 'Invalid technology version' : 'Technology version is required'
-        }
-        return this.techState !== false && this.versionState !== false
-      }
-      return true
-    },
-    create () {
-      if (this.check()) {
-        let creationData = {
-          target_port: this.targetPortId
-        }
-        if (this.name === 'vulnerability') {
-          creationData['cve'] = this.newCve
-        } else if (this.name === 'technology') {
-          creationData['name'] = this.newTech
-          creationData['version'] = this.newVersion
-        }
-        this.post(`/api/target-${this.endpoint}/`, creationData, creationData[this.field], `New target ${this.name} created successfully`)
-          .then(() => this.fetchData())
-        this.clean()
-      }
-    },
-    deleteItem () {
-      this.delete(`/api/target-${this.endpoint}/${this.selectedItem.id}/`).then(() => this.fetchData())
-    },
-    selectItem (item) {
-      this.selectedItem = item
-    },
-    clean () {
-      this.newEndpoint = null
-      this.invalidEndpoint = 'Endpoint is required'
-      this.endpointState = null
-      this.newCve = null
-      this.invalidCve = 'CVE is required'
-      this.cveState = null
-      this.newTech = null
-      this.newVersion = null
-      this.invalidTech = 'Technology name is required'
-      this.invalidVersion = 'Technology version is required'
-      this.techState = null
-      this.versionState = null
-    },
-    cleanSelection () {
-      this.selectedItem = null
-    }
-  }
-}
-</script>
\ No newline at end of file
diff --git a/rekono/frontend/src/modals/TargetPortDetails.vue b/rekono/frontend/src/modals/TargetPortDetails.vue
index 65574b685..3cf35be9d 100644
--- a/rekono/frontend/src/modals/TargetPortDetails.vue
+++ b/rekono/frontend/src/modals/TargetPortDetails.vue
@@ -1,17 +1,23 @@
 <template>
   <b-modal :id="id" :title="'Target Port ' + targetPort.port" header-bg-variant="dark" header-text-variant="light" size="lg" hide-footer @hidden="close()">
     <b-tabs fill card active-nav-item-class="text-danger">
+      <b-tab title-link-class="text-secondary">
+        <template #title>
+          <b-icon icon="shield-lock-fill"/> Authentication
+        </template>
+        <target-port-detail :targetPortId="targetPort.id" endpoint="authentications" name="authentication" field="name" :fields="authenticationFields"/>
+      </b-tab>
       <b-tab title-link-class="text-secondary">
         <template #title>
           <b-icon icon="cpu-fill"/> Technologies
         </template>
-        <target-port-detail :targetPortId="targetPort.id" endpoint="technologies" name="technology" field="name" :fields="targetTechnologiesFields"/>
+        <target-port-detail :targetPortId="targetPort.id" endpoint="target-technologies" name="technology" field="name" :fields="targetTechnologiesFields"/>
       </b-tab>
       <b-tab title-link-class="text-secondary">
         <template #title>
           <b-icon icon="bug-fill"/> Vulnerabilities
         </template>
-        <target-port-detail :targetPortId="targetPort.id" endpoint="vulnerabilities" name="vulnerability" field="cve" :fields="targetVulnerabilitiesFields"/>
+        <target-port-detail :targetPortId="targetPort.id" endpoint="target-vulnerabilities" name="vulnerability" field="cve" :fields="targetVulnerabilitiesFields"/>
       </b-tab>
     </b-tabs>
   </b-modal>
@@ -19,7 +25,7 @@
 
 <script>
 import RekonoApi from '@/backend/RekonoApi';
-import TargetPortDetail from './TargetPortDetail';
+import TargetPortDetail from '@/common/TargetPortDetail';
 export default {
   name: 'targetPortModal',
   mixins: [RekonoApi],
@@ -29,6 +35,11 @@ export default {
   },
   data () {
     return {
+      authenticationFields: [
+        { key: 'name' },
+        { key: 'credential', label: 'Secret' },
+        { key: 'type' }
+      ],
       targetTechnologiesFields: [
         { key: 'name', label: 'Technology' },
         { key: 'version' }
diff --git a/rekono/frontend/src/modals/TargetTechnology.vue b/rekono/frontend/src/modals/TargetTechnology.vue
new file mode 100644
index 000000000..9bb80317f
--- /dev/null
+++ b/rekono/frontend/src/modals/TargetTechnology.vue
@@ -0,0 +1,87 @@
+<template>
+  <b-row>
+    <b-col cols="1">
+      <b-input-group-append v-b-tooltip.hover title="SearchSploit needs technology information to look for known exploits when previous executions didn't find any">
+        <b-button variant="outline">
+          <b-icon icon="info-circle-fill" variant="info"/>
+        </b-button>
+      </b-input-group-append>
+    </b-col>
+    <b-col cols="9">
+      <b-form>
+        <b-row>
+          <b-col cols="6">
+            <b-form-group :invalid-feedback="invalidTech">
+              <b-form-input type="text" v-model="newTech" placeholder="Technology Name" :state="techState" autofocus/>
+            </b-form-group>
+          </b-col>
+          <b-col cols="6">
+            <b-form-group :invalid-feedback="invalidVersion">
+              <b-form-input type="text" v-model="newVersion" placeholder="Technology Version" :state="versionState" autofocus/>
+            </b-form-group>
+          </b-col>
+        </b-row>
+      </b-form>
+    </b-col>
+    <b-col cols="2">
+      <b-button variant="outline" @click="create" v-b-tooltip.hover title="Create">
+        <p class="h3"><b-icon variant="success" icon="plus-square-fill"/></p>
+      </b-button>
+    </b-col>
+  </b-row>
+</template>
+
+<script>
+import RekonoApi from '@/backend/RekonoApi';
+export default {
+  name: 'targetTechnology',
+  mixins: [RekonoApi],
+  props: {
+    targetPortId: Number,
+  },
+  data () {
+    return {
+      newTech: null,
+      newVersion: null,
+      invalidTech: 'Technology name is required',
+      invalidVersion: 'Technology version is required',
+      techState: null,
+      versionState: null
+    }
+  },
+  methods: {
+    check () {
+      this.techState = null
+      this.versionState = null
+      if (!this.validateName(this.newTech)) {
+        this.techState = false
+        this.invalidName = this.newTech && this.newTech.length > 0 ? 'Invalid technology name' : 'Technology name is required'
+      }
+      if (!this.validateName(this.newVersion)) {
+        this.versionState = false
+        this.invalidVersion = this.newVersion && this.newVersion.length > 0 ? 'Invalid technology version' : 'Technology version is required'
+      }
+      return this.techState !== false && this.versionState !== false
+    },
+    create () {
+      if (this.check()) {
+        this.post(
+          '/api/target-technologies/',
+          { target_port: this.targetPortId, name: this.newTech, version: this.newVersion },
+          this.newTech, 'New target technology created successfully'
+        )
+          .then(() => this.$emit('update'))
+        this.clean()
+      }
+    },
+    clean () {
+      this.newTech = null
+      this.newVersion = null
+      this.invalidTech = 'Technology name is required'
+      this.invalidVersion = 'Technology version is required'
+      this.techState = null
+      this.versionState = null
+    }
+  }
+}
+</script>
\ No newline at end of file
diff --git a/rekono/frontend/src/modals/TargetVulnerability.vue b/rekono/frontend/src/modals/TargetVulnerability.vue
new file mode 100644
index 000000000..3d3b38c38
--- /dev/null
+++ b/rekono/frontend/src/modals/TargetVulnerability.vue
@@ -0,0 +1,68 @@
+<template>
+  <b-row>
+    <b-col cols="1">
+      <b-input-group-append v-b-tooltip.hover title="Metasploit needs CVEs to look for known exploits when previous executions didn't find any">
+        <b-button variant="outline">
+          <b-icon icon="info-circle-fill" variant="info"/>
+        </b-button>
+      </b-input-group-append>
+    </b-col>
+    <b-col cols="9">
+      <b-form>
+        <b-form-group :invalid-feedback="invalidCve">
+          <b-form-input type="text" v-model="newCve" placeholder="CVE" :state="cveState" autofocus/>
+        </b-form-group>
+      </b-form>
+    </b-col>
+    <b-col cols="2">
+      <b-button variant="outline" @click="create" v-b-tooltip.hover title="Create">
+        <p class="h3"><b-icon variant="success" icon="plus-square-fill"/></p>
+      </b-button>
+    </b-col>
+  </b-row>
+</template>
+
+<script>
+import RekonoApi from '@/backend/RekonoApi';
+export default {
+  name: 'targetVulnerability',
+  mixins: [RekonoApi],
+  props: {
+    targetPortId: Number,
+  },
+  data () {
+    return {
+      newCve: null,
+      invalidCve: 'CVE is required',
+      cveState: null,
+    }
+  },
+  methods: {
+    check () {
+      this.cveState = null
+      if (!this.validateCve(this.newCve)) {
+        this.cveState = false
+        this.invalidCve = this.newCve && this.newCve.length > 0 ? 'Invalid CVE' : 'CVE is required'
+        return false
+      }
+      return true
+    },
+    create () {
+      if (this.check()) {
+        this.post(
+          '/api/target-vulnerabilities/',
+          { target_port: this.targetPortId, cve: this.newCve },
+          this.newCve, 'New target vulnerability created successfully'
+        )
+          .then(() => this.$emit('update'))
+        this.clean()
+      }
+    },
+    clean () {
+      this.newCve = null
+      this.invalidCve = 'CVE is required'
+      this.cveState = null
+    }
+  }
+}
+</script>
\ No newline at end of file
diff --git a/rekono/input_types/enums.py b/rekono/input_types/enums.py
index bea88375d..9ccc02ddd 100644
--- a/rekono/input_types/enums.py
+++ b/rekono/input_types/enums.py
@@ -35,3 +35,7 @@ class InputKeyword(Enum):
     CVE = 13
     EXPLOIT = 14
     WORDLIST = 15
+    COOKIE_NAME = 16
+    TOKEN = 17
+    CREDENTIAL_TYPE = 18
+    CREDENTIAL_TYPE_LOWER = 19
diff --git a/rekono/input_types/fixtures/1_input_types.json b/rekono/input_types/fixtures/1_input_types.json
index 7a68dfc1a..bfd7d87ba 100644
--- a/rekono/input_types/fixtures/1_input_types.json
+++ b/rekono/input_types/fixtures/1_input_types.json
@@ -79,5 +79,14 @@
             "related_model": null,
             "callback_target": "resources.wordlist"
         }
+    },
+    {
+        "model": "input_types.inputtype",
+        "pk": 10,
+        "fields": {
+            "name": "Authentication",
+            "related_model": "authentications.authentication",
+            "callback_target": null
+        }
     }
 ]
\ No newline at end of file
diff --git a/rekono/input_types/utils.py b/rekono/input_types/utils.py
index 395322c70..c7c0dc246 100644
--- a/rekono/input_types/utils.py
+++ b/rekono/input_types/utils.py
@@ -50,10 +50,11 @@ def get_relations_between_input_types() -> Dict[InputType, List[InputType]]:
         if model:
             for field in model._meta.get_fields():                              # For each related model field
                 # Check if field is a ForeignKey to a BaseInput model
-                if isinstance(field, models.ForeignKey) and issubclass(field.related_model, BaseInput):
-                    # Found a related input type. Get 'related_model' reference from field metadata
-                    related_model = f'{field.related_model._meta.app_label}.{field.related_model._meta.model_name}'
+                if field.__class__ == models.ForeignKey and issubclass(field.related_model, BaseInput):
                     # Search InputType by related model
-                    related_type = InputType.objects.get(related_model=related_model)
-                    relations[it].append(related_type)
+                    related_type = InputType.objects.filter(
+                        related_model=f'{field.related_model._meta.app_label}.{field.related_model._meta.model_name}'
+                    )
+                    if related_type.exists():
+                        relations[it].append(related_type.first())
     return relations
diff --git a/rekono/processes/executor/executor.py b/rekono/processes/executor/executor.py
index d83688a37..26a71333f 100644
--- a/rekono/processes/executor/executor.py
+++ b/rekono/processes/executor/executor.py
@@ -90,7 +90,7 @@ def execute(task: Task) -> None:
     for job in execution_plan:                                                  # For each planned jobs
         # Check unneeded target types, due to dependencies with previous jobs
         covered_targets = [i.callback_target for i in job.dependencies_coverage if i.callback_target is not None]
-        # Wordlist never will be covered by dependencies, so they are included directly in targets
+        # Wordlists are included in targets because they never will be covered by dependencies
         targets = list(task.wordlists.all())
         app_label = Target._meta.app_label
         if f'{app_label}.{Target._meta.model_name}' not in covered_targets:     # Target is not covered by dependencies
diff --git a/rekono/rekono/settings.py b/rekono/rekono/settings.py
index 8cebbc7e0..a518b5092 100644
--- a/rekono/rekono/settings.py
+++ b/rekono/rekono/settings.py
@@ -93,6 +93,7 @@
     'rest_framework.authtoken',
     'rest_framework_simplejwt',
     'rest_framework_simplejwt.token_blacklist',
+    'authentications',
     'executions',
     'findings',
     'input_types',
diff --git a/rekono/rekono/urls.py b/rekono/rekono/urls.py
index be220312b..66232b298 100644
--- a/rekono/rekono/urls.py
+++ b/rekono/rekono/urls.py
@@ -24,7 +24,8 @@
 
 urlpatterns = [
     path('admin/', admin.site.urls),                                            # Admin site
-    path('api/', include('executions.urls')),                                   # Rekono API Rest
+    path('api/', include('authentications.urls')),                              # Rekono API Rest
+    path('api/', include('executions.urls')),
     path('api/', include('findings.urls')),
     path('api/', include('processes.urls')),
     path('api/', include('projects.urls')),
diff --git a/rekono/security/input_validation.py b/rekono/security/input_validation.py
index 53d04b11e..3760c4fad 100644
--- a/rekono/security/input_validation.py
+++ b/rekono/security/input_validation.py
@@ -9,9 +9,10 @@
 NAME_REGEX = r'[\wÀ-ÿ\s\.\-\[\]()]{0,100}'                                      # Regex for names validation
 TEXT_REGEX = r'[\wÀ-ÿ\s\.:,+\-\'"?¿¡!#%$€\[\]()]{0,300}'                        # Regex for text validation
 PATH_REGEX = r'[\w\./#?&%$\\]{0,500}'                                           # Regex for path validation
-CVE_REGEX = r'CVE-[0-9]{4}-[0-9]{1,7}'                                          # Regex for CVE validation
-DD_KEY_REGEX = r'[0-9a-z]{40}'                                                  # Regex for Defect-Dojo key validation
-TELEGRAM_TOKEN_REGEX = r'[0-9]{10}:[\w\-]{35}'                                  # Regex for Telegram token validation
+CVE_REGEX = r'CVE-\d{4}-\d{1,7}'                                                # Regex for CVE validation
+DD_KEY_REGEX = r'[\da-z]{40}'                                                   # Regex for Defect-Dojo key validation
+TELEGRAM_TOKEN_REGEX = r'\d{10}:[\w\-]{35}'                                     # Regex for Telegram token validation
+CREDENTIAL_REGEX = r'[\d\w\./\-=\+,:<>¿?¡!#&$()\[\]\{\}\*]{1,500}'              # Regex for credentials validation
 
 
 def validate_text_value(value: str, regex: str) -> None:
@@ -89,13 +90,41 @@ def validate_cve(value: str) -> None:
 
 
 def validate_telegram_token(value: str) -> None:
+    '''Validate if Telegram token is valid based on regex.
+
+    Args:
+        value (str): Telegram token value
+
+    Raises:
+        ValidationError: Raised if value doesn't match the expected regex
+    '''
     validate_text_value(value, TELEGRAM_TOKEN_REGEX)
 
 
 def validate_defect_dojo_api_key(value: str) -> None:
+    '''Validate if Defect-Dojo API key is valid based on regex.
+
+    Args:
+        value (str): Defect-Dojo API key value
+
+    Raises:
+        ValidationError: Raised if value doesn't match the expected regex
+    '''
     validate_text_value(value, DD_KEY_REGEX)
 
 
+def validate_credential(value: str) -> None:
+    '''Validate if credential is valid based on regex.
+
+    Args:
+        value (str): Credential value
+
+    Raises:
+        ValidationError: Raised if value doesn't match the expected regex
+    '''
+    validate_text_value(value, CREDENTIAL_REGEX)
+
+
 def validate_number(value: int) -> None:
     '''Validate if number is valid based on min and max values.
 
diff --git a/rekono/system/serializers.py b/rekono/system/serializers.py
index 81f299972..5615b4637 100644
--- a/rekono/system/serializers.py
+++ b/rekono/system/serializers.py
@@ -1,50 +1,24 @@
-from typing import Optional
+from typing import Any, Dict, Optional
 
+from api.serializers import ProtectedStringValueField
 from defectdojo.api import DefectDojo
-from drf_spectacular.types import OpenApiTypes
-from drf_spectacular.utils import extend_schema_field
 from rest_framework import serializers
+from security.input_validation import (validate_defect_dojo_api_key,
+                                       validate_telegram_token)
 from telegram_bot.bot import get_telegram_bot_name
 
 from system.models import System
 
 
-@extend_schema_field(OpenApiTypes.STR)
-class ProtectedValueField(serializers.Field):
-    '''Serializer field to manage protected system values.'''
-
-    def to_representation(self, value: str) -> str:
-        '''Return text value to send to the client.
-
-        Args:
-            value (str): Internal text value
-
-        Returns:
-            str: Text value that contains multiple '*' characters
-        '''
-        return '*' * len(value)
-
-    def to_internal_value(self, value: str) -> str:
-        '''Return text value to be stored in database.
-
-        Args:
-            value (str): Text value provided by the client
-
-        Returns:
-            str: Text value to be stored. Save value than the provided one.
-        '''
-        return value
-
-
 class SystemSerializer(serializers.ModelSerializer):
     '''Serializer to manage system settings via API.'''
 
     # Telegram bot name obtained automatically using the Telegram token
     telegram_bot_name = serializers.SerializerMethodField(method_name='get_telegram_bot_name', read_only=True)
     # Telegram token in a protected way
-    telegram_bot_token = ProtectedValueField(required=False, allow_null=True)
+    telegram_bot_token = ProtectedStringValueField(required=False, allow_null=True)
     # Defect-Dojo APi key in a protected way
-    defect_dojo_api_key = ProtectedValueField(required=False, allow_null=True)
+    defect_dojo_api_key = ProtectedStringValueField(required=False, allow_null=True)
     # Indicate if Defect-Dojo integration is available using the URL and the API key
     defect_dojo_enabled = serializers.SerializerMethodField(method_name='is_defect_dojo_enabled', read_only=True)
 
@@ -81,3 +55,22 @@ def get_telegram_bot_name(self, instance: System) -> Optional[str]:
             Optional[str]: Telegram bot name
         '''
         return get_telegram_bot_name()
+
+    def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]:
+        '''Validate the provided data before use it.
+
+        Args:
+            attrs (Dict[str, Any]): Provided data
+
+        Raises:
+            ValidationError: Raised if provided data is invalid
+
+        Returns:
+            Dict[str, Any]: Data after validation process
+        '''
+        attrs = super().validate(attrs)
+        if 'telegram_bot_token' in attrs:
+            validate_telegram_token(attrs.get('telegram_bot_token', ''))
+        if 'defect_dojo_api_key' in attrs:
+            validate_defect_dojo_api_key(attrs.get('defect_dojo_api_key', ''))
+        return attrs
diff --git a/rekono/targets/serializers.py b/rekono/targets/serializers.py
index 010265f5d..91c18c791 100644
--- a/rekono/targets/serializers.py
+++ b/rekono/targets/serializers.py
@@ -16,10 +16,10 @@ class Meta:
 
         model = TargetPort
         fields = (                                                              # Target port fields exposed via API
-            'id', 'target', 'port', 'target_technologies', 'target_vulnerabilities'
+            'id', 'target', 'port', 'target_technologies', 'target_vulnerabilities', 'authentication'
         )
         # Read only fields
-        read_only_fields = ('target_technologies', 'target_vulnerabilities')
+        read_only_fields = ('target_technologies', 'target_vulnerabilities', 'authentication')
 
     def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]:
         '''Validate the provided data before use it.
diff --git a/rekono/tools/fixtures/3_configurations.json b/rekono/tools/fixtures/3_configurations.json
index 8965068e6..333c2d74b 100644
--- a/rekono/tools/fixtures/3_configurations.json
+++ b/rekono/tools/fixtures/3_configurations.json
@@ -145,7 +145,7 @@
         "fields": {
             "tool": 2,
             "name": "Standard wordlist",
-            "arguments": "{url} {intensity} -o {output} --format=json {wordlist}",
+            "arguments": "{url} {intensity} -o {output} --format=json {wordlist} {authentication} {cookie}",
             "default": true
         }
     },
@@ -155,7 +155,7 @@
         "fields": {
             "tool": 2,
             "name": "Wordlist with extensions",
-            "arguments": "{url} {intensity} -o {output} --format=json --force-extensions {wordlist}",
+            "arguments": "{url} {intensity} -o {output} --format=json --force-extensions {wordlist} {authentication} {cookie}",
             "default": false
         }
     },
@@ -165,7 +165,7 @@
         "fields": {
             "tool": 2,
             "name": "Recursive",
-            "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --recursion-depth=10 {wordlist}",
+            "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}",
             "default": false
         }
     },
@@ -175,7 +175,7 @@
         "fields": {
             "tool": 2,
             "name": "Deep recursive",
-            "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --depth-recursive --recursion-depth=10 {wordlist}",
+            "arguments": "{url} {intensity} -o {output} --format=json --force-recursive --depth-recursive --recursion-depth=10 {wordlist} {authentication} {cookie}",
             "default": false
         }
     },
@@ -205,7 +205,7 @@
         "fields": {
             "tool": 4,
             "name": "Web scan",
-            "arguments": "{url} -Format xml -output {output}",
+            "arguments": "{url} -Format xml -output {output} {authentication} {cookie}",
             "default": true
         }
     },
@@ -245,7 +245,7 @@
         "fields": {
             "tool": 8,
             "name": "Active scan",
-            "arguments": "-cmd {url} -quickprogress -quickout {output}",
+            "arguments": "{authentication} {cookie} {command} -cmd {url} -quickprogress -quickout {output}",
             "default": true
         }
     },
@@ -315,7 +315,7 @@
         "fields": {
             "tool": 14,
             "name": "Joomla scan",
-            "arguments": "{url}",
+            "arguments": "{url} {cookie}",
             "default": true
         }
     },
@@ -345,7 +345,7 @@
         "fields": {
             "tool": 17,
             "name": "List shares",
-            "arguments": "{host} {port}",
+            "arguments": "{host} {port} {authentication}",
             "default": true
         }
     },
@@ -355,7 +355,7 @@
         "fields": {
             "tool": 17,
             "name": "List shares and directories recursively",
-            "arguments": "{host} {port} -R",
+            "arguments": "{host} {port} -R {authentication}",
             "default": false
         }
     },
diff --git a/rekono/tools/fixtures/4_arguments.json b/rekono/tools/fixtures/4_arguments.json
index 0fdc5f2e1..101bbad07 100644
--- a/rekono/tools/fixtures/4_arguments.json
+++ b/rekono/tools/fixtures/4_arguments.json
@@ -229,5 +229,93 @@
             "required": false,
             "multiple": false
         }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 22,
+        "fields": {
+            "tool": 2,
+            "name": "authentication",
+            "argument": "--auth={token} --auth-type={credential_type_lower}",
+            "required": false,
+            "multiple": false
+        }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 23,
+        "fields": {
+            "tool": 2,
+            "name": "cookie",
+            "argument": "--cookie='{cookie_name}={secret}'",
+            "required": false,
+            "multiple": false
+        }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 24,
+        "fields": {
+            "tool": 14,
+            "name": "cookie",
+            "argument": "--cookie '{cookie_name}={secret}'",
+            "required": false,
+            "multiple": false
+        }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 25,
+        "fields": {
+            "tool": 17,
+            "name": "authentication",
+            "argument": "-u {username} -p {secret}",
+            "required": false,
+            "multiple": false
+        }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 26,
+        "fields": {
+            "tool": 4,
+            "name": "authentication",
+            "argument": "-id {username}:{secret}",
+            "required": false,
+            "multiple": false
+        }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 27,
+        "fields": {
+            "tool": 4,
+            "name": "cookie",
+            "argument": "-Option STATIC-COOKIE='{cookie_name}={secret}'",
+            "required": false,
+            "multiple": false
+        }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 28,
+        "fields": {
+            "tool": 8,
+            "name": "authentication",
+            "argument": "ZAP_AUTH_HEADER='Authorization' ZAP_AUTH_HEADER_VALUE='{credential_type} {token}'",
+            "required": false,
+            "multiple": false
+        }
+    },
+    {
+        "model": "tools.argument",
+        "pk": 29,
+        "fields": {
+            "tool": 8,
+            "name": "cookie",
+            "argument": "ZAP_AUTH_HEADER='Cookie' ZAP_AUTH_HEADER_VALUE='{cookie_name}={secret}'",
+            "required": false,
+            "multiple": false
+        }
     }
 ]
\ No newline at end of file
diff --git a/rekono/tools/fixtures/5_inputs.json b/rekono/tools/fixtures/5_inputs.json
index 80108555e..a2c8da75b 100644
--- a/rekono/tools/fixtures/5_inputs.json
+++ b/rekono/tools/fixtures/5_inputs.json
@@ -328,5 +328,85 @@
             "filter": "microsoft-ds",
             "order": 1
         }
+    },
+    {
+        "model": "tools.input",
+        "pk": 34,
+        "fields": {
+            "argument": 22,
+            "type": 10,
+            "filter": "!cookie",
+            "order": 1
+        }
+    },
+    {
+        "model": "tools.input",
+        "pk": 35,
+        "fields": {
+            "argument": 23,
+            "type": 10,
+            "filter": "cookie",
+            "order": 1
+        }
+    },
+    {
+        "model": "tools.input",
+        "pk": 36,
+        "fields": {
+            "argument": 24,
+            "type": 10,
+            "filter": "cookie",
+            "order": 1
+        }
+    },
+    {
+        "model": "tools.input",
+        "pk": 37,
+        "fields": {
+            "argument": 25,
+            "type": 10,
+            "filter": "basic",
+            "order": 1
+        }
+    },
+    {
+        "model": "tools.input",
+        "pk": 38,
+        "fields": {
+            "argument": 26,
+            "type": 10,
+            "filter": "basic",
+            "order": 1
+        }
+    },
+    {
+        "model": "tools.input",
+        "pk": 39,
+        "fields": {
+            "argument": 27,
+            "type": 10,
+            "filter": "cookie",
+            "order": 1
+        }
+    },
+    {
+        "model": "tools.input",
+        "pk": 40,
+        "fields": {
+            "argument": 28,
+            "type": 10,
+            "filter": "!cookie",
+            "order": 1
+        }
+    },
+    {
+        "model": "tools.input",
+        "pk": 41,
+        "fields": {
+            "argument": 29,
+            "type": 10,
+            "filter": "cookie",
+            "order": 1
+        }
     }
 ]
\ No newline at end of file
diff --git a/rekono/tools/tools/base_tool.py b/rekono/tools/tools/base_tool.py
index 34a25711d..8450a7b76 100644
--- a/rekono/tools/tools/base_tool.py
+++ b/rekono/tools/tools/base_tool.py
@@ -4,24 +4,26 @@
 import shutil
 import subprocess
 import uuid
-from typing import Any, Dict, List, Tuple, Union, cast
+from typing import Any, Dict, List, Optional, Tuple, Union, cast
 
+from authentications.models import Authentication
 from django.db.models import Model
 from django.db.models.fields.related_descriptors import \
     ReverseManyToOneDescriptor
 from django.db.models.query_utils import DeferredAttribute
 from django.utils import timezone
 from executions.models import Execution
-from findings.models import Finding, Vulnerability
+from findings.models import Finding, Port, Vulnerability
 from findings.queue import producer
 from findings.utils import get_unique_filter
 from input_types.base import BaseInput
+from rekono.settings import REPORTS_DIR, TESTING
+from targets.models import TargetPort
 from tasks.enums import Status
+
 from tools.exceptions import ToolExecutionException
 from tools.models import Argument, Input, Intensity
 
-from rekono.settings import REPORTS_DIR, TESTING
-
 logger = logging.getLogger()                                                    # Rekono logger
 
 
@@ -179,6 +181,35 @@ def process_argument(
                         break
         return found, command
 
+    def get_authentication(
+        self,
+        targets_list: List[BaseInput],
+        findings_list: List[Finding]
+    ) -> Optional[Authentication]:
+        '''Get authentication for a given list of targets and findings.
+
+        Args:
+            targets_list (List[BaseInput]): Targets list
+            findings_list (List[Finding]): Findings list
+
+        Returns:
+            Optional[Authentication]: Authentication entity if found
+        '''
+        for ports in [
+            [p for p in findings_list if isinstance(p, Port)],                  # Get Ports from findings list
+            [p for p in targets_list if isinstance(p, TargetPort)],             # Get TargetPorts from targets list
+        ]:
+            if len(ports) > 0:                                                  # Ports found
+                if len(ports) == 1:                                             # Only one port
+                    authentications = Authentication.objects.filter(            # Look for authentication entity
+                        target_port__target=self.execution.task.target,
+                        target_port__port=cast(Union[Port, TargetPort], ports[0]).port
+                    )
+                    if authentications.exists():                                # Authentication found
+                        return authentications.first()
+                break
+        return None
+
     def get_arguments(self, targets: List[BaseInput], previous_findings: List[Finding]) -> List[str]:
         '''Get tool arguments for the tool command.
 
@@ -193,9 +224,13 @@ def get_arguments(self, targets: List[BaseInput], previous_findings: List[Findin
             List[str]: List of tool arguments to use in the tool execution
         '''
         command = {
+            'command': self.tool.command,                                       # Add tool command to the arguments
             'intensity': self.intensity.argument,                               # Add intensity config to the arguments
             'output': self.path_output if self.file_output_enabled else ''      # Add output config to the arguments
         }
+        authentication = self.get_authentication(targets, previous_findings)    # Search related authentication instance
+        if authentication:                                                      # Authentication exists
+            previous_findings.append(authentication)                            # Add authentication instance
         for argument in self.arguments:                                         # For each tool argument
             found, command = self.process_argument(
                 argument,
@@ -213,7 +248,7 @@ def get_arguments(self, targets: List[BaseInput], previous_findings: List[Findin
         # Format configuration arguments with the built tool arguments
         args = self.configuration.arguments.format(**command)
         # Split arguments by whitespaces taking into account the arguments between quotes
-        return [arg.replace('\'', '').replace('"', '') for arg in re.findall(r'[\'"]{1}.+?[\'"]{1}|[^\s]+', args)]
+        return [arg for arg in re.findall(r'[\'"].+[\'"]|[^\s]+', args)]
 
     def check_arguments(self, targets: List[BaseInput], findings: List[Finding]) -> bool:
         '''Check if given resources (targets, resources and findings) lists are enough to execute the tool.
@@ -263,7 +298,8 @@ def tool_execution(self, arguments: List[str], targets: List[BaseInput], previou
         Returns:
             str: Plain output of the tool execution
         '''
-        arguments.insert(0, self.tool.command)                                  # Combine tool command with arguments
+        if self.tool.command not in arguments:
+            arguments.insert(0, self.tool.command)                              # Combine tool command with arguments
         logger.info(f'[Tool] Running: {" ".join(arguments)}')
         if hasattr(self, 'run_directory'):
             # Execute the tool in directory


Test patch:
diff --git a/rekono/testing/api/test_authentications.py b/rekono/testing/api/test_authentications.py
new file mode 100644
index 000000000..7d2befb9a
--- /dev/null
+++ b/rekono/testing/api/test_authentications.py
@@ -0,0 +1,57 @@
+from authentications.enums import AuthenticationType
+from authentications.models import Authentication
+from testing.api.base import RekonoApiTestCase
+
+
+class AuthenticationTest(RekonoApiTestCase):
+    '''Test cases for Authentication entity.'''
+
+    def setUp(self) -> None:
+        '''Create initial data before run tests.'''
+        self.endpoint = '/api/authentications/'                                 # Authentication API endpoint
+        super().setUp()
+        super().initialize_environment()                                        # Initialize testing environment
+        # Data for testing
+        self.data = {
+            'target_port': self.target_port.id,
+            'name': 'admin',
+            'credential': 'admin',
+            'type': AuthenticationType.BASIC
+        }
+
+    def test_create(self) -> None:
+        '''Test authentication creation.'''
+        self.data['name'] = 'regularuser'
+        expected = self.data.copy()
+        expected['credential'] = len(self.data['credential']) * '*'             # Credential will be protected
+        # Create new authentication
+        content = self.api_test(self.client.post, self.endpoint, 201, data=self.data, expected=expected)
+        self.api_test(self.client.get, f'{self.endpoint}{content["id"]}/', expected=expected)
+
+    def test_create_with_invalid_target_port(self) -> None:
+        '''Test authentication creation with invalid target port.'''
+        self.data['target_port'] = self.target_port
+        Authentication.objects.create(**self.data)                              # Create authentication
+        self.data['target_port'] = self.target_port.id
+        self.api_test(self.client.post, self.endpoint, 400, data=self.data)     # Authentication already exists
+
+    def test_create_with_invalid_credential(self) -> None:
+        '''Test authentication creation with invalid credential.'''
+        self.data['credential'] = ';reverseshell'
+        self.api_test(self.client.post, self.endpoint, 400, data=self.data)     # Invalid credential value
+
+    def test_delete(self) -> None:
+        '''Test authentication deletion feature.'''
+        self.data['target_port'] = self.target_port
+        authentication = Authentication.objects.create(**self.data)             # Create authentication
+        self.api_test(self.client.delete, f'{self.endpoint}{authentication.id}/', 204)      # Delete authentication
+        self.api_test(self.client.get, f'{self.endpoint}{authentication.id}/', 404)
+
+    def test_model_representation(self) -> None:
+        '''Test __str__ method for authentication model.'''
+        self.data['target_port'] = self.target_port
+        authentication = Authentication.objects.create(**self.data)             # Create authentication
+        self.models = {                                                         # Models to test __str__ method
+            authentication: f'{self.target_port.__str__()} - {authentication.name}'
+        }
+        super().test_model_representation()
diff --git a/rekono/testing/api/test_system.py b/rekono/testing/api/test_system.py
index 076a697f9..7851b8502 100644
--- a/rekono/testing/api/test_system.py
+++ b/rekono/testing/api/test_system.py
@@ -36,3 +36,9 @@ def test_update_system(self) -> None:
         new_system.pop('id')                                                    # Remove some fields from the new data
         new_system.pop('defect_dojo_api_key')
         self.api_test(self.client.put, self.endpoint, data=new_system, expected=expected)   # Update system config
+
+    def test_invalid_update_system(self) -> None:
+        '''Test update system feature with invalid data.'''
+        new_system = self.system_data.copy()
+        new_system['telegram_bot_token'] = 'invalidtelegramtoken'               # Invalid Telegram token
+        self.api_test(self.client.put, self.endpoint, 400, data=new_system)     # Try to update system config
diff --git a/rekono/testing/executions/test_base_tool.py b/rekono/testing/executions/test_base_tool.py
index 6c939b6e2..cc31fdb4d 100644
--- a/rekono/testing/executions/test_base_tool.py
+++ b/rekono/testing/executions/test_base_tool.py
@@ -3,6 +3,8 @@
 from unittest import mock
 
 import django_rq
+from authentications.enums import AuthenticationType
+from authentications.models import Authentication
 from django.utils import timezone
 from findings.enums import DataType, Protocol, Severity
 from findings.models import (OSINT, Credential, Exploit, Finding, Host, Path,
@@ -47,12 +49,18 @@ def setUp(self) -> None:
             name='Test',
             tool=self.nmap,
             arguments=(
-                '{intensity} {test_osint} {test_only_host} {test_host} {test_port} {test_path} '
-                '{test_technology} {test_credential} {test_vulnerability} {test_exploit} {test_wordlist}'
+                '{intensity} {test_osint} {test_only_host} {test_host} {test_port} {test_path} {test_technology} '
+                '{test_credential} {test_vulnerability} {test_exploit} {test_wordlist} {test_authentication}'
             )
         )
+        self.authentication_argument = Argument.objects.create(
+            tool=self.nmap,
+            name='test_authentication',
+            argument='--credential {secret}',
+            required=False
+        )
         # Initialize auxiliary lists to help data usage
-        self.arguments: List[Argument] = []
+        self.arguments: List[Argument] = [self.authentication_argument]
         self.targets: List[BaseInput] = []
         self.all_findings: List[Finding] = []
         self.required_findings: List[Finding] = []
@@ -75,6 +83,7 @@ def setUp(self) -> None:
             '--version 1.0.0', '--email test@test.test', '--username test', '--secret test',
             '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}'
         ]).split(' ')
+        # Expected required arguments
         self.required_expected = ' '.join([
             '-T3', '--osint http://scanme.nmap.org/', '--only-host 45.33.32.156', '--host 45.33.32.156',
             '--port 443', '--port-commas 80,443', '--tech Wordpress',
@@ -118,14 +127,17 @@ def create_targets(self) -> None:
         # Target filtered due to target type. Private IP required
         target_filtered = Target.objects.create(project=self.project, target='scanme.nmap.org', type=TargetType.DOMAIN)
         target = Target.objects.create(project=self.project, target='45.33.32.156', type=TargetType.PUBLIC_IP)
-        target_port_http = TargetPort.objects.create(target=target, port=80)
+        self.target_port_http = TargetPort.objects.create(target=target, port=80)
         target_port_https = TargetPort.objects.create(target=target, port=443)
         target_technology = TargetTechnology.objects.create(
-            target_port=target_port_http,
+            target_port=self.target_port_http,
             name='Wordpress',
             version='1.0.0'
         )
-        target_vulnerability = TargetVulnerability.objects.create(target_port=target_port_http, cve='CVE-2021-44228')
+        target_vulnerability = TargetVulnerability.objects.create(
+            target_port=self.target_port_http,
+            cve='CVE-2021-44228'
+        )
         user = User.objects.create_superuser('rekono', 'rekono@rekono.rekono', 'rekono')
         task = Task.objects.create(
             target=target,
@@ -153,7 +165,7 @@ def create_targets(self) -> None:
         )
         self.targets.extend([
             target_filtered, target,
-            target_port_http, target_port_https,
+            self.target_port_http, target_port_https,
             target_technology,
             target_vulnerability
         ])
@@ -443,6 +455,35 @@ def test_get_arguments_using_all_findings_and_alternative_filters(self) -> None:
         ]).split(' ')
         self.assertEqual(expected, arguments)
 
+    def test_get_arguments_using_authentication(self) -> None:
+        '''Test get_arguments feature using authentication.'''
+        # Filter targets and findings to include only one port
+        self.targets = [t for t in self.targets if not isinstance(t, TargetPort) or t == self.target_port_http]
+        self.all_findings = [f for f in self.all_findings if not isinstance(f, Port) or f == self.port]
+        authentication_input = Input.objects.create(                            # Create authentication input
+            argument=self.authentication_argument,
+            type=InputType.objects.get(name='Authentication'),
+            filter='!cookie'
+        )
+        Authentication.objects.create(                                          # Create authentication entity
+            target_port=self.target_port_http,
+            name='sessionid', credential='token',
+            type=AuthenticationType.COOKIE
+        )
+        expected_list = [
+            '-T3', '--osint http://scanme.nmap.org/', '--only-host 45.33.32.156', '--host 45.33.32.156',
+            '--port 80', '--port-commas 80', '--endpoint /robots.txt', '--tech Wordpress',
+            '--version 1.0.0', '--email test@test.test', '--username test', '--secret test',
+            '--vuln CVE-2021-44228', '--exploit Test', f'--wordlist {self.wordlist.path}'
+        ]
+        arguments = self.tool_instance.get_arguments(self.targets, self.all_findings)
+        self.assertEqual(' '.join(expected_list).split(' '), arguments)         # Authentication is filter by type
+        authentication_input.filter = 'cookie'                                  # Change input filter to cookie
+        authentication_input.save(update_fields=['filter'])
+        expected_list.append('--credential token')                              # Add expected argument
+        arguments = self.tool_instance.get_arguments(self.targets, self.all_findings)
+        self.assertEqual(' '.join(expected_list).split(' '), arguments)
+
     def test_get_arguments_using_required_findings(self) -> None:
         '''Test get_arguments feature using only the required findings.'''
         # Change findings relations for more test situations
@@ -591,3 +632,24 @@ def test_process_findings_with_errors_in_defectdojo_test_type_creation(self) ->
     def test_process_findings_with_unvailable_defectdojo(self) -> None:
         '''Test process_findings feature with unavailable Defect-Dojo instance.'''
         self.process_findings(False)
+
+    def test_get_authentication(self) -> None:
+        '''Test get_authentication feature'''
+        # No authentication found
+        search = self.tool_instance.get_authentication([self.target_port_http], [self.port])
+        self.assertEqual(None, search)
+        authentication = Authentication.objects.create(                         # Create authentication for testing
+            target_port=self.target_port_http,
+            name='test',
+            credential='test',
+            type=AuthenticationType.BASIC
+        )
+        # Get authentication based on Port
+        search = self.tool_instance.get_authentication([self.target_port_http], [self.port])
+        self.assertEqual(authentication, search)
+        # Get authentication based on TargetPort
+        search = self.tool_instance.get_authentication([self.target_port_http], [])
+        self.assertEqual(authentication, search)
+        # No authentication found
+        search = self.tool_instance.get_authentication([], [])
+        self.assertEqual(None, search)
diff --git a/rekono/testing/executions/test_executions_from_findings.py b/rekono/testing/executions/test_executions_from_findings.py
index e32cba0b8..11730d0ba 100644
--- a/rekono/testing/executions/test_executions_from_findings.py
+++ b/rekono/testing/executions/test_executions_from_findings.py
@@ -78,7 +78,7 @@ def create_finding(self, finding_type: Model, **fields: Any) -> BaseInput:
         return finding
 
     def test_with_findings(self) -> None:
-        '''Test get_executions_from_findings feature with findings. Simulates new executions from previous findings.'''
+        '''Test get_executions_from_findings feature with findings.'''
         # Host 1 with some endpoints in some ports
         host_1 = self.create_finding(Host, address='10.10.10.1')
         port_1_1 = self.create_finding(Port, host=host_1, port=22)
@@ -159,7 +159,7 @@ def test_with_findings(self) -> None:
         self.assertEqual(expected, executions)
 
     def test_with_only_one_finding_type(self) -> None:
-        '''Test get_executions_from_findings feature with findings. Simulates new executions from previous findings.'''
+        '''Test get_executions_from_findings feature with findings.'''
         host_1 = self.create_finding(Host, address='10.10.10.1')
         host_2 = self.create_finding(Host, address='10.10.10.2')
         host_3 = self.create_finding(Host, address='10.10.10.3')
@@ -170,8 +170,8 @@ def test_with_only_one_finding_type(self) -> None:
         self.assertEqual(expected, executions)
 
     def test_with_targets(self) -> None:
-        '''Test get_executions_from_findings feature with targets. Simulates initial new executions.'''
-        # Target ports with some target endpoints
+        '''Test get_executions_from_findings feature with targets.'''
+        # Target ports
         tp_1 = TargetPort.objects.create(target=self.target, port=22)
         tp_2 = TargetPort.objects.create(target=self.target, port=80)
         tp_3 = TargetPort.objects.create(target=self.target, port=443)