diff --git a/gso/__init__.py b/gso/__init__.py
index 307827996c61b8e639428cbf9798abea341ff98b..0227c19d7d29838dc2952ac34abb13d6a9ebc3ea 100644
--- a/gso/__init__.py
+++ b/gso/__init__.py
@@ -1,5 +1,7 @@
 """The main entrypoint for :term:`GSO`, and the different ways in which it can be run."""
 
+from gso import monkeypatches  # noqa: F401, isort:skip
+
 import typer
 from orchestrator import OrchestratorCore, app_settings
 from orchestrator.cli.main import app as cli_app
diff --git a/gso/api/v1/imports.py b/gso/api/v1/imports.py
index 03eb49596e6da0aeaa4f87ec0ba72e513c6d07a8..684d7b7eb43bd878b5d3919a768f7a70ab33172c 100644
--- a/gso/api/v1/imports.py
+++ b/gso/api/v1/imports.py
@@ -6,10 +6,10 @@ from uuid import UUID
 
 from fastapi import Depends, HTTPException, status
 from fastapi.routing import APIRouter
-from orchestrator.security import opa_security_default
 from orchestrator.services import processes
 from pydantic import BaseModel, root_validator, validator
 
+from gso.auth.security import opa_security_default
 from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity
 from gso.products.product_blocks.router import RouterRole, RouterVendor
 from gso.products.product_blocks.site import SiteTier
diff --git a/gso/api/v1/subscriptions.py b/gso/api/v1/subscriptions.py
index 438e40885a9c4d6d82543434563f8fdf029ae65d..24c9307f1ce67371ea3f89c6f0888380ad7ea09c 100644
--- a/gso/api/v1/subscriptions.py
+++ b/gso/api/v1/subscriptions.py
@@ -6,9 +6,9 @@ from fastapi import Depends, status
 from fastapi.routing import APIRouter
 from orchestrator.domain import SubscriptionModel
 from orchestrator.schemas import SubscriptionDomainModelSchema
-from orchestrator.security import opa_security_default
 from orchestrator.services.subscriptions import build_extended_domain_model
 
+from gso.auth.security import opa_security_default
 from gso.services.subscriptions import get_active_router_subscriptions
 
 router = APIRouter(
diff --git a/gso/auth/__init__.py b/gso/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d87d539d946d6583659c37e7c7fd024ca5ceb31c
--- /dev/null
+++ b/gso/auth/__init__.py
@@ -0,0 +1 @@
+"""Authentication and authorization integration for OAuth2, OIDC, and OPA."""
diff --git a/gso/auth/oidc_policy_helper.py b/gso/auth/oidc_policy_helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9219cddabb8d0538cd7e484d9ea9d3941d0f037
--- /dev/null
+++ b/gso/auth/oidc_policy_helper.py
@@ -0,0 +1,443 @@
+"""OpenID Connect and Open Policy Agent Integration for GSO Application.
+
+This module provides helper functions and classes for handling OpenID Connect (OIDC) and
+Open Policy Agent (OPA) related functionalities within the GSO application. It includes
+implementations for OIDC-based user authentication and user information modeling. Additionally,
+it facilitates making authorization decisions based on policies defined in OPA. Key components
+comprise OIDCUser, OIDCUserModel, OPAResult, and opa_decision. These elements integrate with
+FastAPI to ensure secure API development.
+"""
+
+import re
+import ssl
+from collections.abc import AsyncGenerator, Awaitable, Callable, Mapping
+from http import HTTPStatus
+from json import JSONDecodeError
+from typing import Any, ClassVar, cast
+
+from fastapi.exceptions import HTTPException
+from fastapi.param_functions import Depends
+from fastapi.requests import Request
+from fastapi.security.http import HTTPBearer
+from httpx import AsyncClient, NetworkError
+from pydantic import BaseModel
+from starlette.requests import ClientDisconnect
+from structlog import get_logger
+
+from gso.auth.settings import oauth2lib_settings
+
+logger = get_logger(__name__)
+
+HTTPX_SSL_CONTEXT = ssl.create_default_context()  # https://github.com/encode/httpx/issues/838
+
+
+class InvalidScopeValueError(ValueError):
+    """Exception raised for invalid scope values in OIDC."""
+
+
+class OIDCUserModel(dict):
+    """The standard claims of a OIDCUserModel object. Defined per `Section 5.1`_ and AAI attributes.
+
+    .. _`Section 5.1`: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
+    """
+
+    #: registered claims that OIDCUserModel supports
+    REGISTERED_CLAIMS: ClassVar[list[str]] = [
+        "sub",
+        "name",
+        "given_name",
+        "family_name",
+        "middle_name",
+        "nickname",
+        "preferred_username",
+        "profile",
+        "picture",
+        "website",
+        "email",
+        "email_verified",
+        "gender",
+        "birthdate",
+        "zoneinfo",
+        "locale",
+        "phone_number",
+        "phone_number_verified",
+        "address",
+        "updated_at",
+    ]
+
+    def __getattr__(self, key: str) -> Any:
+        """Get an attribute value using key.
+
+        Overrides the default behavior to return the value from the dictionary
+        if the attribute is one of the registered claims or raises an AttributeError
+        if the key is not found.
+
+        Args:
+        ----
+        key: The attribute name to retrieve.
+
+        Returns:
+        -------
+        The value of the attribute if it exists, otherwise raises AttributeError.
+        """
+        try:
+            return object.__getattribute__(self, key)
+        except AttributeError as error:
+            if key in self.REGISTERED_CLAIMS:
+                return self.get(key)
+            raise error from None
+
+    @property
+    def user_name(self) -> str:
+        """Return the username of the user."""
+        if "user_name" in self.keys():
+            return cast(str, self["user_name"])
+        if "unspecified_id" in self.keys():
+            return cast(str, self["unspecified_id"])
+        return ""
+
+    @property
+    def display_name(self) -> str:
+        """Return the display name of the user."""
+        return self.get("display_name", "")
+
+    @property
+    def principal_name(self) -> str:
+        """Return the principal name of the user."""
+        return self.get("eduperson_principal_name", "")
+
+    @property
+    def scopes(self) -> set[str]:
+        """Return the scopes of the user."""
+        scope_value = self.get("scope")
+        if scope_value is None:
+            return set()
+
+        if isinstance(scope_value, list):
+            return {item for item in scope_value if isinstance(item, str)}
+        if isinstance(scope_value, str):
+            return set(filter(None, re.split("[ ,]", scope_value)))
+
+        message = f"Invalid scope value: {scope_value}"
+        raise InvalidScopeValueError(message)
+
+
+async def _make_async_client() -> AsyncGenerator[AsyncClient, None]:
+    async with AsyncClient(http1=True, verify=HTTPX_SSL_CONTEXT) as client:
+        yield client
+
+
+class OIDCConfig(BaseModel):
+    """Configuration for OpenID Connect (OIDC) authentication and token validation."""
+
+    issuer: str
+    authorization_endpoint: str
+    token_endpoint: str
+    userinfo_endpoint: str
+    introspect_endpoint: str | None = None
+    introspection_endpoint: str | None = None
+    jwks_uri: str
+    response_types_supported: list[str]
+    response_modes_supported: list[str]
+    grant_types_supported: list[str]
+    subject_types_supported: list[str]
+    id_token_signing_alg_values_supported: list[str]
+    scopes_supported: list[str]
+    token_endpoint_auth_methods_supported: list[str]
+    claims_supported: list[str]
+    claims_parameter_supported: bool
+    request_parameter_supported: bool
+    code_challenge_methods_supported: list[str]
+
+
+class OPAResult(BaseModel):
+    """Represents the outcome of an authorization decision made by the Open Policy Agent (OPA).
+
+    Attributes
+    ----------
+    - result (bool): Indicates whether the access request is allowed or denied.
+    - decision_id (str): A unique identifier for the decision made by OPA.
+    """
+
+    result: bool = False
+    decision_id: str
+
+
+class OIDCUser(HTTPBearer):
+    """OIDCUser class extends the :term:`HTTPBearer` class to do extra verification.
+
+    The class will act as follows:
+        1. Validate the Credentials at AAI proxy by calling the UserInfo endpoint
+    """
+
+    openid_config: OIDCConfig | None = None
+    openid_url: str
+    resource_server_id: str
+    resource_server_secret: str
+
+    def __init__(
+        self,
+        openid_url: str,
+        resource_server_id: str,
+        resource_server_secret: str,
+        *,
+        auto_error: bool = True,
+        scheme_name: str | None = None,
+    ):
+        """Set up OIDCUser with specified OpenID Connect configurations and credentials."""
+        super().__init__(auto_error=auto_error)
+        self.openid_url = openid_url
+        self.resource_server_id = resource_server_id
+        self.resource_server_secret = resource_server_secret
+        self.scheme_name = scheme_name or self.__class__.__name__
+
+    async def __call__(  # type: ignore[override]
+        self, request: Request, token: str | None = None
+    ) -> OIDCUserModel | None:
+        """Return the OIDC user from OIDC introspect endpoint.
+
+        This is used as a security module in Fastapi projects
+
+        Args:
+        ----
+            request: Starlette request method.
+            token: Optional value to directly pass a token.
+
+        Returns:
+        -------
+            OIDCUserModel object.
+
+        """
+        if not oauth2lib_settings.OAUTH2_ACTIVE:
+            return None
+
+        async with AsyncClient(http1=True, verify=HTTPX_SSL_CONTEXT) as async_request:
+            await self.check_openid_config(async_request)
+
+            if not token:
+                credentials = await super().__call__(request)
+                if not credentials:
+                    return None
+                token = credentials.credentials
+
+            intercepted_token = await self.introspect_token(async_request, token)
+
+            if "active" not in intercepted_token:
+                logger.error("Token doesn't have the mandatory 'active' key, probably caused by a caching problem")
+                raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Missing active key")
+            if not intercepted_token.get("active", False):
+                logger.info("User is not active", url=request.url, user_info=intercepted_token)
+                raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="User is not active")
+
+            user_info = await self.userinfo(async_request, token)
+
+            logger.debug("OIDCUserModel object.", intercepted_token=intercepted_token)
+            return user_info
+
+    async def check_openid_config(self, async_request: AsyncClient) -> None:
+        """Check of openid config is loaded and load if not."""
+        if self.openid_config is not None:
+            return
+
+        response = await async_request.get(self.openid_url + "/.well-known/openid-configuration")
+        self.openid_config = OIDCConfig.parse_obj(response.json())
+
+    async def userinfo(self, async_request: AsyncClient, token: str) -> OIDCUserModel:
+        """Get the userinfo from the openid server.
+
+        Args:
+        ----
+            async_request: The async request
+            token: the access_token
+
+        Returns:
+        -------
+            OIDCUserModel from openid server
+
+        """
+        await self.check_openid_config(async_request)
+        assert self.openid_config, "OpenID config should be loaded"  # noqa: S101
+
+        response = await async_request.post(
+            self.openid_config.userinfo_endpoint,
+            data={"token": token},
+            headers={"Authorization": f"Bearer {token}"},
+        )
+        try:
+            data = dict(response.json())
+        except JSONDecodeError as err:
+            logger.debug(
+                "Unable to parse userinfo response",
+                detail=response.text,
+                resource_server_id=self.resource_server_id,
+                openid_url=self.openid_url,
+            )
+            raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail=response.text) from err
+        logger.debug("Response from openid userinfo", response=data)
+
+        if response.status_code not in range(200, 300):
+            logger.debug(
+                "Userinfo cannot find an active token, user unauthorized",
+                detail=response.text,
+                resource_server_id=self.resource_server_id,
+                openid_url=self.openid_url,
+            )
+            raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail=response.text)
+
+        return OIDCUserModel(data)
+
+    async def introspect_token(self, async_request: AsyncClient, token: str) -> dict:
+        """Introspect the access token to see if it is a valid token.
+
+        Args:
+        ----
+            async_request: The async request
+            token: the access_token
+
+        Returns:
+        -------
+            dict from openid server
+
+        """
+        await self.check_openid_config(async_request)
+        assert self.openid_config, "OpenID config should be loaded"  # noqa: S101
+
+        endpoint = self.openid_config.introspect_endpoint or self.openid_config.introspection_endpoint or ""
+        response = await async_request.post(
+            endpoint,
+            data={"token": token, "client_id": self.resource_server_id},
+            headers={"Content-Type": "application/x-www-form-urlencoded"},
+        )
+
+        try:
+            data = dict(response.json())
+        except JSONDecodeError as err:
+            logger.debug(
+                "Unable to parse introspect response",
+                detail=response.text,
+                resource_server_id=self.resource_server_id,
+                openid_url=self.openid_url,
+            )
+            raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail=response.text) from err
+
+        logger.debug("Response from openid introspect", response=data)
+
+        if response.status_code not in range(200, 300):
+            logger.debug(
+                "Introspect cannot find an active token, user unauthorized",
+                detail=response.text,
+                resource_server_id=self.resource_server_id,
+                openid_url=self.openid_url,
+            )
+            raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail=response.text)
+
+        return data
+
+
+async def _get_decision(async_request: AsyncClient, opa_url: str, opa_input: dict) -> OPAResult:
+    logger.debug("Posting input json to Policy agent", opa_url=opa_url, input=opa_input)
+    try:
+        response = await async_request.post(opa_url, json=opa_input)
+    except (NetworkError, TypeError) as exc:
+        logger.debug("Could not get decision from policy agent", error=str(exc))
+        raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail="Policy agent is unavailable") from exc
+
+    result = response.json()
+    logger.debug("Received response from Policy agent", response=result)
+    return OPAResult(result=result["result"]["allow"], decision_id=result["decision_id"])
+
+
+def _evaluate_decision(decision: OPAResult, *, auto_error: bool, **context: dict[str, Any]) -> bool:
+    did = decision.decision_id
+
+    if decision.result:
+        logger.debug("User is authorized to access the resource", decision_id=did, **context)
+        return True
+
+    logger.debug("User is not allowed to access the resource", decision_id=did, **context)
+    if not auto_error:
+        return False
+
+    raise HTTPException(
+        status_code=HTTPStatus.FORBIDDEN,
+        detail=f"User is not allowed to access resource: {context.get('resource')} Decision was taken with id: {did}",
+    )
+
+
+def opa_decision(
+    opa_url: str,
+    oidc_security: OIDCUser,
+    *,
+    auto_error: bool = True,
+    opa_kwargs: Mapping[str, str] | None = None,
+) -> Callable[[Request, OIDCUserModel, AsyncClient], Awaitable[bool | None]]:
+    """Create a decision function for Open Policy Agent (OPA) authorization checks.
+
+    This function generates an asynchronous decision function that can be used in FastAPI endpoints
+    to authorize requests based on OPA policies. It utilizes OIDC for user information and makes a
+    call to the OPA service to determine authorization.
+
+    Args:
+    ----
+    opa_url: URL of the Open Policy Agent service.
+    oidc_security: An instance of OIDCUser for user authentication.
+    auto_error: If True, automatically raises an HTTPException on authorization failure.
+    opa_kwargs: Additional keyword arguments to be passed to the OPA input.
+
+    Returns:
+    -------
+    An asynchronous decision function that can be used as a dependency in FastAPI endpoints.
+    """
+
+    async def _opa_decision(
+        request: Request,
+        user_info: OIDCUserModel = Depends(oidc_security),  # noqa: B008
+        async_request: AsyncClient = Depends(_make_async_client),  # noqa: B008
+    ) -> bool | None:
+        """Check OIDCUserModel against the OPA policy.
+
+        This is used as a security module in Fastapi projects
+        This method will make an async call towards the Policy agent.
+
+        Args:
+        ----
+            request: Request object that will be used to retrieve request metadata.
+            user_info: The OIDCUserModel object that will be checked
+            async_request: The :term:`httpx` client.
+        """
+        if not (oauth2lib_settings.OAUTH2_ACTIVE and oauth2lib_settings.OAUTH2_AUTHORIZATION_ACTIVE):
+            return None
+
+        try:
+            json = await request.json()
+        # Silencing the Decode error or Type error when request.json() does not return anything sane.
+        # Some requests do not have a json response therefore as this code gets called on every request
+        # we need to suppress the `None` case (TypeError) or the `other than json` case (JSONDecodeError)
+        # Suppress AttributeError in case of websocket request, it doesn't have .json
+        except (JSONDecodeError, TypeError, ClientDisconnect, AttributeError):
+            json = {}
+
+        # defaulting to GET request method for WebSocket request, it doesn't have .method
+        request_method = request.method if hasattr(request, "method") else "GET"
+        opa_input = {
+            "input": {
+                **(opa_kwargs or {}),
+                **user_info,
+                "resource": request.url.path,
+                "method": request_method,
+                "arguments": {"path": request.path_params, "query": {**request.query_params}, "json": json},
+            }
+        }
+
+        decision = await _get_decision(async_request, opa_url, opa_input)
+
+        context = {
+            "resource": opa_input["input"]["resource"],
+            "method": opa_input["input"]["method"],
+            "user_info": user_info,
+            "input": opa_input,
+            "url": request.url,
+        }
+        return _evaluate_decision(decision, auto_error=auto_error, **context)
+
+    return _opa_decision
diff --git a/gso/auth/security.py b/gso/auth/security.py
new file mode 100644
index 0000000000000000000000000000000000000000..16065e467e02176d92df20563c4c3e0f56845667
--- /dev/null
+++ b/gso/auth/security.py
@@ -0,0 +1,41 @@
+"""Module for initializing OAuth client credentials and OIDC user."""
+
+from authlib.integrations.starlette_client import OAuth
+from nwastdlib.url import URL
+
+from gso.auth.oidc_policy_helper import HTTPX_SSL_CONTEXT, OIDCUser, opa_decision
+from gso.auth.settings import oauth2_settings
+
+oauth_client_credentials = OAuth()
+
+well_known_endpoint = URL(oauth2_settings.OIDC_CONF_WELL_KNOWN_URL)
+
+oauth_client_credentials.register(
+    "connext",
+    server_metadata_url=well_known_endpoint / ".well-known" / "openid-configuration",
+    client_id=oauth2_settings.OAUTH2_RESOURCE_SERVER_ID,
+    client_secret=oauth2_settings.OAUTH2_RESOURCE_SERVER_SECRET,
+    request_token_params={"grant_type": "client_credentials"},
+    client_kwargs={"verify": HTTPX_SSL_CONTEXT},
+)
+
+oidc_user = OIDCUser(
+    oauth2_settings.OIDC_CONF_WELL_KNOWN_URL,
+    oauth2_settings.OAUTH2_RESOURCE_SERVER_ID,
+    oauth2_settings.OAUTH2_RESOURCE_SERVER_SECRET,
+)
+
+opa_security_default = opa_decision(oauth2_settings.OPA_URL, oidc_user)
+
+
+def get_oidc_user() -> OIDCUser:
+    """Retrieve the global OIDCUser instance.
+
+    This function returns the instance of OIDCUser initialized in the module.
+    It is typically used for accessing the OIDCUser across different parts of the application.
+
+    Returns
+    -------
+        OIDCUser: The instance of OIDCUser configured with OAuth2 settings.
+    """
+    return oidc_user
diff --git a/gso/auth/settings.py b/gso/auth/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8b281253f3548efb6d5a6f87c03329f496ec114
--- /dev/null
+++ b/gso/auth/settings.py
@@ -0,0 +1,41 @@
+"""Security configurations and utilities for the GSO application. Handles OAuth2 and OpenID Connect.
+
+authentication and authorization, including token validation and user authentication. Integrates
+with external authentication providers for enhanced security management.
+
+Todo:
+----
+Remove token and sensitive data from OPA console and API.
+
+"""
+
+from pydantic import BaseSettings, Field
+
+
+class Oauth2LibSettings(BaseSettings):
+    """Common settings for applications depending on oauth2."""
+
+    ENVIRONMENT: str = "local"
+    SERVICE_NAME: str = ""
+    MUTATIONS_ENABLED: bool = False
+    ENVIRONMENT_IGNORE_MUTATION_DISABLED: list[str] = Field(
+        default_factory=list, description="Environments for which to allow unauthenticated mutations"
+    )
+    OAUTH2_ACTIVE: bool = True
+    OAUTH2_AUTHORIZATION_ACTIVE: bool = True
+
+
+oauth2lib_settings = Oauth2LibSettings()
+
+
+class Oauth2Settings(BaseSettings):
+    """Configuration settings for OAuth2 and OpenID Connect (OIDC)."""
+
+    OAUTH2_RESOURCE_SERVER_ID: str = ""
+    OAUTH2_RESOURCE_SERVER_SECRET: str = ""
+    OAUTH2_TOKEN_URL: str = ""
+    OIDC_CONF_WELL_KNOWN_URL: str = ""
+    OPA_URL: str = "http://localhost:8181/v1/data/gap/gso/api/access"
+
+
+oauth2_settings = Oauth2Settings()
diff --git a/gso/monkeypatches.py b/gso/monkeypatches.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e94f50bdd27288e4ce7d829036ffbc8f022ef20
--- /dev/null
+++ b/gso/monkeypatches.py
@@ -0,0 +1,17 @@
+"""Override certain classes and settings in the oauth2_lib.fastapi package with custom implementations.
+
+This adjustment is typically done to extend or modify the functionality of the original
+oauth2_lib package to meet specific requirements of the gso application.
+"""
+
+import oauth2_lib.fastapi
+import oauth2_lib.settings
+
+from gso.auth.oidc_policy_helper import HTTPX_SSL_CONTEXT, OIDCUser, OIDCUserModel, opa_decision
+from gso.auth.settings import oauth2lib_settings
+
+oauth2_lib.fastapi.OIDCUser = OIDCUser  # type: ignore[assignment, misc]
+oauth2_lib.fastapi.OIDCUserModel = OIDCUserModel  # type: ignore[assignment, misc]
+oauth2_lib.fastapi.opa_decision = opa_decision  # type: ignore[assignment]
+oauth2_lib.fastapi.HTTPX_SSL_CONTEXT = HTTPX_SSL_CONTEXT
+oauth2_lib.settings.oauth2lib_settings = oauth2lib_settings  # type: ignore[assignment]
diff --git a/pyproject.toml b/pyproject.toml
index 27544d313cbf350daba1ab147e844e4ade062689..e345710ba1f602942d24b792649ea61c0d4f5ccb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,7 +38,8 @@ ignore = [
     "N805",
     "PLR0913",
     "PLR0904",
-    "PLW1514"
+    "PLW1514",
+    "S106",
 ]
 line-length = 120
 select = [
diff --git a/requirements.txt b/requirements.txt
index ca077d368e511aeb84a585f696abd685697eedcf..87dc706a5986c5833967ea7fae184a5228dad1ca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -18,3 +18,4 @@ ruff==0.1.5
 sphinx==7.2.6
 sphinx-rtd-theme==1.3.0
 urllib3_mock==0.3.3
+pytest-asyncio==0.23.3
diff --git a/test/auth/__init__.py b/test/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/auth/test_oidc_policy_helper.py b/test/auth/test_oidc_policy_helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..a47e2d0fc714ec08b2fee8b8a4d247c1bc950c72
--- /dev/null
+++ b/test/auth/test_oidc_policy_helper.py
@@ -0,0 +1,284 @@
+from http import HTTPStatus
+from unittest.mock import AsyncMock, MagicMock, Mock, patch
+
+import pytest
+from fastapi import HTTPException, Request
+from httpx import AsyncClient, NetworkError, Response
+
+from gso.auth.oidc_policy_helper import (
+    OIDCConfig,
+    OIDCUser,
+    OIDCUserModel,
+    OPAResult,
+    _evaluate_decision,
+    _get_decision,
+    opa_decision,
+)
+from gso.auth.settings import oauth2lib_settings
+
+
+@pytest.fixture(scope="module", autouse=True)
+def _enable_oath2(_database, db_uri):
+    oauth2lib_settings.OAUTH2_ACTIVE = True
+
+    yield
+
+    oauth2lib_settings.OAUTH2_ACTIVE = False
+
+
+@pytest.fixture()
+def mock_openid_config():
+    return {
+        "issuer": "https://example.proxy.aai.geant.org",
+        "authorization_endpoint": "https://example.proxy.aai.geant.org/auth",
+        "token_endpoint": "https://example.proxy.aai.geant.org/token",
+        "userinfo_endpoint": "https://example.proxy.aai.geant.org/userinfo",
+        "introspect_endpoint": "https://example.proxy.aai.geant.org/introspect",
+        "jwks_uri": "https://example.proxy.aai.geant.org/jwks",
+        "response_types_supported": ["code"],
+        "response_modes_supported": ["query"],
+        "grant_types_supported": ["authorization_code"],
+        "subject_types_supported": ["public"],
+        "id_token_signing_alg_values_supported": ["RS256"],
+        "scopes_supported": ["openid"],
+        "token_endpoint_auth_methods_supported": ["client_secret_basic"],
+        "claims_supported": ["sub", "name", "email"],
+        "claims_parameter_supported": True,
+        "request_parameter_supported": True,
+        "code_challenge_methods_supported": ["S256"],
+    }
+
+
+@pytest.fixture()
+def oidc_user(mock_openid_config):
+    user = OIDCUser(
+        openid_url="https://example.proxy.aai.geant.org",
+        resource_server_id="resource_server",
+        resource_server_secret="secret",
+    )
+    user.openid_config = OIDCConfig.parse_obj(mock_openid_config)
+    return user
+
+
+@pytest.fixture()
+def mock_request():
+    request = Mock(spec=Request)
+    request.method = "GET"
+    request.url.path = "/some/path"
+    request.json = AsyncMock(return_value={"key": "value"})
+    request.path_params = {}
+    request.query_params = {}
+    request.headers.get = Mock(return_value="Bearer testtoken1212121")
+    return request
+
+
+@pytest.fixture()
+def mock_oidc_user():
+    oidc_user = AsyncMock(
+        OIDCUser, openid_url="https://example.com", resource_server_id="test", resource_server_secret="secret"
+    )
+    oidc_user.__call__ = AsyncMock(return_value=OIDCUserModel({"sub": "123", "name": "John Doe"}))
+    return oidc_user
+
+
+@pytest.fixture()
+def mock_async_client():
+    return AsyncClient(verify=False)  # noqa: S501
+
+
+@pytest.mark.asyncio()
+async def test_introspect_token_success(oidc_user, mock_async_client):
+    mock_response_data = {"active": True, "sub": "123"}
+    mock_async_client.post = AsyncMock(return_value=Response(200, json=mock_response_data))
+
+    result = await oidc_user.introspect_token(mock_async_client, "test_token")
+
+    assert result == mock_response_data
+
+
+@pytest.mark.asyncio()
+async def test_introspect_token_json_decode_error(oidc_user, mock_async_client):
+    mock_async_client.post = AsyncMock(return_value=Response(200, content=b"not a json"))
+
+    with pytest.raises(HTTPException) as exc_info:
+        await oidc_user.introspect_token(mock_async_client, "test_token")
+
+    assert exc_info.value.status_code == HTTPStatus.UNAUTHORIZED
+
+
+@pytest.mark.asyncio()
+async def test_introspect_token_http_error(oidc_user, mock_async_client):
+    mock_async_client.post = AsyncMock(return_value=Response(400, json={"error": "invalid_request"}))
+
+    with pytest.raises(HTTPException) as exc_info:
+        await oidc_user.introspect_token(mock_async_client, "test_token")
+
+    assert exc_info.value.status_code == HTTPStatus.UNAUTHORIZED
+
+
+@pytest.mark.asyncio()
+async def test_introspect_token_unauthorized(oidc_user, mock_async_client):
+    mock_async_client.post = AsyncMock(return_value=Response(401, json={"detail": "Invalid token"}))
+
+    with pytest.raises(HTTPException) as exc_info:
+        await oidc_user.introspect_token(mock_async_client, "test_token")
+
+    assert exc_info.value.status_code == HTTPStatus.UNAUTHORIZED
+    assert "Invalid token" in str(exc_info.value.detail)
+
+
+@pytest.mark.asyncio()
+async def test_userinfo_success(oidc_user, mock_async_client):
+    mock_response = {"sub": "1234", "name": "John Doe", "email": "johndoe@example.com"}
+    mock_async_client.post = AsyncMock(return_value=Response(200, json=mock_response))
+
+    response = await oidc_user.userinfo(mock_async_client, "test_token")
+
+    assert isinstance(response, OIDCUserModel)
+    assert response["sub"] == "1234"
+    assert response["name"] == "John Doe"
+    assert response["email"] == "johndoe@example.com"
+
+
+@pytest.mark.asyncio()
+async def test_opa_decision_success(mock_request, mock_async_client):
+    mock_user_info = OIDCUserModel({"sub": "123", "name": "John Doe", "email": "johndoe@example.com"})
+
+    mock_oidc_user = AsyncMock(spec=OIDCUser)
+    mock_oidc_user.return_value = AsyncMock(return_value=mock_user_info)
+
+    with patch(
+        "gso.auth.oidc_policy_helper._get_decision",
+        return_value=AsyncMock(return_value=OPAResult(result=True, decision_id="1234")),
+    ):
+        decision_function = opa_decision("http://mock-opa-url", oidc_security=mock_oidc_user)
+
+        result = await decision_function(mock_request, mock_user_info, mock_async_client)
+
+        assert result is True
+
+
+@pytest.mark.asyncio()
+async def test_userinfo_unauthorized(oidc_user, mock_async_client):
+    mock_async_client.post = AsyncMock(return_value=Response(401, json={"detail": "Invalid token"}))
+
+    with pytest.raises(HTTPException) as exc_info:
+        await oidc_user.userinfo(mock_async_client, "test_token")
+
+    assert exc_info.value.status_code == HTTPStatus.UNAUTHORIZED
+    assert "Invalid token" in str(exc_info.value.detail)
+
+
+@pytest.mark.asyncio()
+async def test_userinfo_json_decode_error(oidc_user, mock_async_client):
+    mock_async_client.post = AsyncMock(return_value=Response(200, text="not a json"))
+
+    with pytest.raises(HTTPException) as exc_info:
+        await oidc_user.userinfo(mock_async_client, "test_token")
+
+    assert exc_info.value.status_code == HTTPStatus.UNAUTHORIZED
+
+
+@pytest.mark.asyncio()
+async def test_get_decision_success(mock_async_client):
+    mock_async_client.post = AsyncMock(
+        return_value=Response(200, json={"result": {"allow": True}, "decision_id": "123"})
+    )
+
+    opa_url = "http://mock-opa-url"
+    opa_input = {"some_input": "value"}
+    decision = await _get_decision(mock_async_client, opa_url, opa_input)
+
+    assert decision.result is True
+    assert decision.decision_id == "123"
+
+
+@pytest.mark.asyncio()
+async def test_get_decision_network_error(mock_async_client):
+    mock_async_client.post = AsyncMock(side_effect=NetworkError("Network error"))
+
+    opa_url = "http://mock-opa-url"
+    opa_input = {"some_input": "value"}
+
+    with pytest.raises(HTTPException) as exc_info:
+        await _get_decision(mock_async_client, opa_url, opa_input)
+
+    assert exc_info.value.status_code == HTTPStatus.SERVICE_UNAVAILABLE
+    assert exc_info.value.detail == "Policy agent is unavailable"
+
+
+def test_evaluate_decision_allow():
+    decision = OPAResult(result=True, decision_id="123")
+    result = _evaluate_decision(decision, auto_error=True)
+
+    assert result is True
+
+
+def test_evaluate_decision_deny_without_auto_error():
+    decision = OPAResult(result=False, decision_id="123")
+    result = _evaluate_decision(decision, auto_error=False)
+
+    assert result is False
+
+
+def test_evaluate_decision_deny_with_auto_error():
+    decision = OPAResult(result=False, decision_id="123")
+
+    with pytest.raises(HTTPException) as exc_info:
+        _evaluate_decision(decision, auto_error=True)
+
+    assert exc_info.value.status_code == HTTPStatus.FORBIDDEN
+    assert "Decision was taken with id: 123" in str(exc_info.value.detail)
+
+
+@pytest.mark.asyncio()
+async def test_oidc_user_call_with_token(oidc_user, mock_request, mock_async_client):
+    oidc_user.introspect_token = AsyncMock(return_value={"active": True})
+    oidc_user.userinfo = AsyncMock(return_value=OIDCUserModel({"sub": "123", "name": "John Doe"}))
+
+    result = await oidc_user.__call__(mock_request, token="test_token")
+
+    assert isinstance(result, OIDCUserModel)
+    assert result["sub"] == "123"
+    assert result["name"] == "John Doe"
+
+
+@pytest.mark.asyncio()
+async def test_oidc_user_call_inactive_token(oidc_user, mock_request, mock_async_client):
+    oidc_user.introspect_token = AsyncMock(return_value={"active": False})
+
+    with pytest.raises(HTTPException) as exc_info:
+        await oidc_user.__call__(mock_request, token="test_token")
+
+    assert exc_info.value.status_code == HTTPStatus.UNAUTHORIZED
+    assert "User is not active" in str(exc_info.value.detail)
+
+
+@pytest.mark.asyncio()
+async def test_oidc_user_call_no_token(oidc_user, mock_request):
+    with (
+        patch("fastapi.security.http.HTTPBearer.__call__", return_value=None),
+        patch("httpx.AsyncClient.post", new_callable=MagicMock) as mock_post,
+        patch("httpx.AsyncClient.get", new_callable=MagicMock) as mock_get,
+    ):
+        mock_post.return_value = MagicMock(status_code=200, json=lambda: {"active": False})
+        mock_get.return_value = MagicMock(status_code=200, json=lambda: {})
+
+        result = await oidc_user.__call__(mock_request)
+
+    assert result is None
+
+
+@pytest.mark.asyncio()
+async def test_oidc_user_call_token_from_request(oidc_user, mock_request, mock_async_client):
+    mock_request.state.credentials = Mock()
+    mock_request.state.credentials.credentials = "request_token"
+
+    oidc_user.introspect_token = AsyncMock(return_value={"active": True})
+    oidc_user.userinfo = AsyncMock(return_value=OIDCUserModel({"sub": "123", "name": "John Doe"}))
+
+    result = await oidc_user.__call__(mock_request)
+
+    assert isinstance(result, OIDCUserModel)
+    assert result["sub"] == "123"
+    assert result["name"] == "John Doe"
diff --git a/test/conftest.py b/test/conftest.py
index 7d53941eb84cdcf806ce3efa34985570b269973a..779fc39d0f50addb3875baa4a0836adc071f8d86 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -12,7 +12,6 @@ from alembic import command
 from alembic.config import Config
 from faker import Faker
 from faker.providers import BaseProvider
-from oauth2_lib.settings import oauth2lib_settings
 from orchestrator import app_settings
 from orchestrator.db import Database, db
 from orchestrator.db.database import ENGINE_ARGUMENTS, SESSION_ARGUMENTS, BaseModel
@@ -22,6 +21,7 @@ from sqlalchemy.engine import make_url
 from sqlalchemy.orm import scoped_session, sessionmaker
 from starlette.testclient import TestClient
 
+from gso.auth.settings import oauth2lib_settings
 from gso.main import init_gso_app
 from gso.utils.helpers import LAGMember