Skip to content

Authentication Strategies

The authentication module provides pluggable authentication strategies for the WebApp construct.

Overview

Authentication is implemented allowing you to choose between different authentication methods without changing your application code.

Available Strategies

  • AuthType.NONE - No authentication (public access)
  • AuthType.COGNITO - AWS Cognito authentication with OAuth2 (managed login UI)
  • AuthType.INTERNAL_ACCESS - AWS Cognito authentication with external IdP (e.g., EntraID)

AuthType

gds_idea_cdk_constructs.web_app._auth_strategies.AuthType

Bases: StrEnum

Defines the supported authentication types for the WebApp construct.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
class AuthType(StrEnum):
    """Defines the supported authentication types for the WebApp construct."""

    NONE = "none"
    COGNITO = "cognito"
    INTERNAL_ACCESS = "internal-access"

IAuthStrategy (Interface)

gds_idea_cdk_constructs.web_app._auth_strategies.IAuthStrategy

Bases: ABC

Interface for an authentication strategy.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
class IAuthStrategy(ABC):
    """Interface for an authentication strategy."""

    def __init__(
        self, scope: Construct, deployment_config: DeploymentConfig, app_name: str
    ):
        self.scope = scope
        self.deployment_config = deployment_config
        self.app_name = app_name

    @abstractmethod
    def create_listener_action(
        self, target_group: elbv2.IApplicationTargetGroup
    ) -> elbv2.ListenerAction:
        """Return the ALB listener action for this strategy."""
        pass

    @abstractmethod
    def create_outputs(self) -> None:
        """Create any strategy-specific CloudFormation outputs."""
        pass

    @abstractmethod
    def get_minimal_role(self) -> iam.Role:
        """Creates a minimal IAM role configured with permissions
        required by this strategy."""
        pass

    @abstractmethod
    def configure_role_permissions(self, role: iam.IRole) -> None:
        """Grants an existing role the permissions required by this strategy."""
        pass

    @abstractmethod
    def get_environment_variables(self) -> dict[str, str]:
        """Returns environment variables required by this auth strategy."""
        pass

create_listener_action abstractmethod

create_listener_action(
    target_group: IApplicationTargetGroup,
) -> elbv2.ListenerAction

Return the ALB listener action for this strategy.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
@abstractmethod
def create_listener_action(
    self, target_group: elbv2.IApplicationTargetGroup
) -> elbv2.ListenerAction:
    """Return the ALB listener action for this strategy."""
    pass

create_outputs abstractmethod

create_outputs() -> None

Create any strategy-specific CloudFormation outputs.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
@abstractmethod
def create_outputs(self) -> None:
    """Create any strategy-specific CloudFormation outputs."""
    pass

get_minimal_role abstractmethod

get_minimal_role() -> iam.Role

Creates a minimal IAM role configured with permissions required by this strategy.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
@abstractmethod
def get_minimal_role(self) -> iam.Role:
    """Creates a minimal IAM role configured with permissions
    required by this strategy."""
    pass

configure_role_permissions abstractmethod

configure_role_permissions(role: IRole) -> None

Grants an existing role the permissions required by this strategy.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
@abstractmethod
def configure_role_permissions(self, role: iam.IRole) -> None:
    """Grants an existing role the permissions required by this strategy."""
    pass

get_environment_variables abstractmethod

get_environment_variables() -> dict[str, str]

Returns environment variables required by this auth strategy.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
@abstractmethod
def get_environment_variables(self) -> dict[str, str]:
    """Returns environment variables required by this auth strategy."""
    pass

NoAuthStrategy

gds_idea_cdk_constructs.web_app._auth_strategies.NoAuthStrategy

Bases: IAuthStrategy

A strategy for apps with no authentication.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
class NoAuthStrategy(IAuthStrategy):
    """A strategy for apps with no authentication."""

    def create_listener_action(
        self, target_group: elbv2.IApplicationTargetGroup
    ) -> elbv2.ListenerAction:
        """The action is to simply forward traffic."""
        return elbv2.ListenerAction.forward([target_group])

    def create_outputs(self) -> None:
        """This strategy has no specific outputs, so this method does nothing."""
        pass

    def get_minimal_role(self) -> iam.Role:
        """Creates a minimal role with no additional permissions."""
        return iam.Role(
            self.scope,
            "TaskRole",
            assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
        )

    def configure_role_permissions(self, role: iam.IRole) -> None:
        """No auth doesn't need additional permissions."""
        pass

    def get_environment_variables(self) -> dict[str, str]:
        """No auth doesn't need environment variables."""
        return {}

create_listener_action

create_listener_action(
    target_group: IApplicationTargetGroup,
) -> elbv2.ListenerAction

The action is to simply forward traffic.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def create_listener_action(
    self, target_group: elbv2.IApplicationTargetGroup
) -> elbv2.ListenerAction:
    """The action is to simply forward traffic."""
    return elbv2.ListenerAction.forward([target_group])

create_outputs

create_outputs() -> None

This strategy has no specific outputs, so this method does nothing.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def create_outputs(self) -> None:
    """This strategy has no specific outputs, so this method does nothing."""
    pass

get_minimal_role

get_minimal_role() -> iam.Role

Creates a minimal role with no additional permissions.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def get_minimal_role(self) -> iam.Role:
    """Creates a minimal role with no additional permissions."""
    return iam.Role(
        self.scope,
        "TaskRole",
        assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
    )

configure_role_permissions

configure_role_permissions(role: IRole) -> None

No auth doesn't need additional permissions.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def configure_role_permissions(self, role: iam.IRole) -> None:
    """No auth doesn't need additional permissions."""
    pass

get_environment_variables

get_environment_variables() -> dict[str, str]

No auth doesn't need environment variables.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def get_environment_variables(self) -> dict[str, str]:
    """No auth doesn't need environment variables."""
    return {}

BaseCognitoAuthStrategy

gds_idea_cdk_constructs.web_app._auth_strategies.BaseCognitoAuthStrategy

Bases: IAuthStrategy

Base class for Cognito-based authentication strategies.

Provides common setup for User Pool, Domain, and Client creation. Subclasses override _create_user_pool_client() to customize client configuration.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
class BaseCognitoAuthStrategy(IAuthStrategy):
    """Base class for Cognito-based authentication strategies.

    Provides common setup for User Pool, Domain, and Client creation.
    Subclasses override _create_user_pool_client() to customize client configuration.
    """

    def __init__(
        self, scope: Construct, deployment_config: DeploymentConfig, app_name: str
    ):
        super().__init__(scope, deployment_config, app_name)
        self._setup_cognito_resources()

    def _setup_cognito_resources(self) -> None:
        """Looks up and creates all necessary Cognito resources."""
        # Import existing User Pool
        self.user_pool = cognito.UserPool.from_user_pool_id(
            self.scope, "ExistingUserPool", self.deployment_config.user_pool_id
        )

        # Import existing User Pool Domain
        self.user_pool_domain = cognito.UserPoolDomain.from_domain_name(
            self.scope,
            "ExistingCustomCognitoDomain",
            user_pool_domain_name=f"auth.{self.deployment_config.domain_name}",
        )

        # Create User Pool Client (subclass-specific configuration)
        self.cognito_client = self._create_user_pool_client()

        # Allow subclasses to create additional resources (e.g., managed branding)
        self._setup_additional_resources()

    @abstractmethod
    def _create_user_pool_client(self) -> cognito.UserPoolClient:
        """Subclasses implement to customize User Pool Client configuration."""
        pass

    def _setup_additional_resources(self) -> None:
        """Optional hook for subclasses to create additional resources.

        Default implementation does nothing. Override in subclasses if needed
        (e.g., to add managed login branding).
        """
        pass

    def create_listener_action(
        self, target_group: elbv2.IApplicationTargetGroup
    ) -> elbv2.ListenerAction:
        """Returns the Cognito authentication action for the ALB listener."""
        return elbv2_actions.AuthenticateCognitoAction(
            user_pool=self.user_pool,
            user_pool_client=self.cognito_client,
            user_pool_domain=self.user_pool_domain,
            next=elbv2.ListenerAction.forward([target_group]),
        )

    def create_outputs(self) -> None:
        """Creates the Cognito Client ID CloudFormation output."""
        CfnOutput(
            self.scope,
            "CognitoClientId",
            value=self.cognito_client.user_pool_client_id,
            description=f"Cognito Client ID for {self.app_name}",
        )

    def get_minimal_role(self) -> iam.Role:
        """Creates a minimal role with Cognito secret read access."""
        role = iam.Role(
            self.scope,
            "TaskRole",
            assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
        )
        self._grant_secret_access(role)
        return role

    def configure_role_permissions(self, role: iam.IRole) -> None:
        """Grants existing role access to Cognito secrets."""
        self._grant_secret_access(role)

    def get_environment_variables(self) -> dict[str, str]:
        """Returns Cognito secret name for the container."""
        return {"COGNITO_AUTH_SECRET_NAME": f"{self.app_name}/access"}

    def _grant_secret_access(self, role: iam.IRole) -> None:
        """Helper to grant secret read access to a role."""
        secret = secretsmanager.Secret.from_secret_name_v2(
            self.scope, "CognitoAuthSecret", secret_name=f"{self.app_name}/access"
        )
        secret.grant_read(role)

create_listener_action

create_listener_action(
    target_group: IApplicationTargetGroup,
) -> elbv2.ListenerAction

Returns the Cognito authentication action for the ALB listener.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def create_listener_action(
    self, target_group: elbv2.IApplicationTargetGroup
) -> elbv2.ListenerAction:
    """Returns the Cognito authentication action for the ALB listener."""
    return elbv2_actions.AuthenticateCognitoAction(
        user_pool=self.user_pool,
        user_pool_client=self.cognito_client,
        user_pool_domain=self.user_pool_domain,
        next=elbv2.ListenerAction.forward([target_group]),
    )

create_outputs

create_outputs() -> None

Creates the Cognito Client ID CloudFormation output.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def create_outputs(self) -> None:
    """Creates the Cognito Client ID CloudFormation output."""
    CfnOutput(
        self.scope,
        "CognitoClientId",
        value=self.cognito_client.user_pool_client_id,
        description=f"Cognito Client ID for {self.app_name}",
    )

get_minimal_role

get_minimal_role() -> iam.Role

Creates a minimal role with Cognito secret read access.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def get_minimal_role(self) -> iam.Role:
    """Creates a minimal role with Cognito secret read access."""
    role = iam.Role(
        self.scope,
        "TaskRole",
        assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
    )
    self._grant_secret_access(role)
    return role

configure_role_permissions

configure_role_permissions(role: IRole) -> None

Grants existing role access to Cognito secrets.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def configure_role_permissions(self, role: iam.IRole) -> None:
    """Grants existing role access to Cognito secrets."""
    self._grant_secret_access(role)

get_environment_variables

get_environment_variables() -> dict[str, str]

Returns Cognito secret name for the container.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
def get_environment_variables(self) -> dict[str, str]:
    """Returns Cognito secret name for the container."""
    return {"COGNITO_AUTH_SECRET_NAME": f"{self.app_name}/access"}

CognitoManagedLoginAuthStrategy

gds_idea_cdk_constructs.web_app._auth_strategies.CognitoManagedLoginAuthStrategy

Bases: BaseCognitoAuthStrategy

A strategy for apps using Cognito authentication with managed login UI.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
class CognitoManagedLoginAuthStrategy(BaseCognitoAuthStrategy):
    """A strategy for apps using Cognito authentication with managed login UI."""

    def _create_user_pool_client(self) -> cognito.UserPoolClient:
        """Creates a User Pool Client configured to use Cognito managed login."""
        alb_domain_name = f"{self.app_name}.{self.deployment_config.domain_name}"

        return cognito.UserPoolClient(
            self.scope,
            "Client",
            user_pool=self.user_pool,
            user_pool_client_name=f"{self.app_name}UserPoolClient",
            generate_secret=True,
            enable_token_revocation=True,
            supported_identity_providers=[
                cognito.UserPoolClientIdentityProvider.COGNITO
            ],
            auth_flows=cognito.AuthFlow(user=True),
            o_auth=cognito.OAuthSettings(
                flows=cognito.OAuthFlows(authorization_code_grant=True),
                scopes=[
                    cognito.OAuthScope.OPENID,
                    cognito.OAuthScope.EMAIL,
                    cognito.OAuthScope.PROFILE,
                ],
                callback_urls=[f"https://{alb_domain_name}/oauth2/idpresponse"],
                logout_urls=[f"https://{alb_domain_name}"],
            ),
        )

    def _setup_additional_resources(self) -> None:
        """Enable managed login branding with default Cognito styling."""
        cognito.CfnManagedLoginBranding(
            self.scope,
            "ManagedLoginBranding",
            user_pool_id=self.user_pool.user_pool_id,
            client_id=self.cognito_client.user_pool_client_id,
            use_cognito_provided_values=True,
        )

CognitoExternalIdpAuthStrategy

gds_idea_cdk_constructs.web_app._auth_strategies.CognitoExternalIdpAuthStrategy

Bases: BaseCognitoAuthStrategy

A strategy for apps using Cognito with an external identity provider.

This strategy configures the User Pool Client to use an external IdP (e.g., EntraID, Okta) instead of Cognito's managed login UI.

Source code in src/gds_idea_cdk_constructs/web_app/_auth_strategies.py
class CognitoExternalIdpAuthStrategy(BaseCognitoAuthStrategy):
    """A strategy for apps using Cognito with an external identity provider.

    This strategy configures the User Pool Client to use an external IdP
    (e.g., EntraID, Okta) instead of Cognito's managed login UI.
    """

    def _create_user_pool_client(self) -> cognito.UserPoolClient:
        """Creates a User Pool Client configured to use an external IdP."""
        alb_domain_name = f"{self.app_name}.{self.deployment_config.domain_name}"

        return cognito.UserPoolClient(
            self.scope,
            "Client",
            user_pool=self.user_pool,
            user_pool_client_name=f"{self.app_name}UserPoolClient",
            generate_secret=True,
            enable_token_revocation=True,
            supported_identity_providers=[
                cognito.UserPoolClientIdentityProvider.custom(
                    self.deployment_config.external_idp_name
                )
            ],
            auth_flows=cognito.AuthFlow(user=True),
            o_auth=cognito.OAuthSettings(
                flows=cognito.OAuthFlows(authorization_code_grant=True),
                scopes=[
                    cognito.OAuthScope.OPENID,
                    cognito.OAuthScope.EMAIL,
                    cognito.OAuthScope.PROFILE,
                ],
                callback_urls=[f"https://{alb_domain_name}/oauth2/idpresponse"],
                logout_urls=[f"https://{alb_domain_name}"],
            ),
        )

Usage Examples

No Authentication (Public Access)

from gds_idea_cdk_constructs.web_app import WebApp, AuthType

WebApp(
    app,
    deployment_config=deployment_config,
    app_config=app_config,
    authentication=AuthType.NONE,  # Public access
)

Use cases: - Public dashboards - Open APIs - Status pages - Documentation sites

Behavior: - No authentication required - Direct access to application - Minimal IAM permissions - No environment variables added to container

Cognito Authentication

from gds_idea_cdk_constructs.web_app import WebApp, AuthType

WebApp(
    app,
    deployment_config=deployment_config,
    app_config=app_config,
    authentication=AuthType.COGNITO,  # Requires login
)

Use cases: - Internal tools and dashboards - Applications requiring user identity - Protected data visualization - Admin panels

Behavior: - Users must authenticate via Cognito - ALB performs authentication before forwarding requests - OAuth2 authorization code flow - Session cookies for authenticated users - Automatic redirect to Cognito login page

What gets created: - Cognito User Pool Client (OAuth2 client) - Secrets Manager secret for client credentials - ALB listener rule with authentication action - IAM permissions for secret access

Environment variables added to container:

{
    "COGNITO_AUTH_SECRET_NAME": "app-name/access"
}

Authentication Flow (Cognito)

┌─────────┐         ┌─────────┐         ┌─────────┐         ┌─────────┐
│ Browser │         │   ALB   │         │ Cognito │         │   ECS   │
└────┬────┘         └────┬────┘         └────┬────┘         └────┬────┘
     │                   │                   │                   │
     │  1. GET /         │                   │                   │
     ├──────────────────>│                   │                   │
     │                   │                   │                   │
     │  2. No auth cookie, redirect to Cognito                   │
     │<──────────────────┤                   │                   │
     │                   │                   │                   │
     │  3. Login page    │                   │                   │
     ├───────────────────────────────────────>│                   │
     │                   │                   │                   │
     │  4. User logs in  │                   │                   │
     ├───────────────────────────────────────>│                   │
     │                   │                   │                   │
     │  5. OAuth callback with code          │                   │
     │<───────────────────────────────────────┤                   │
     │                   │                   │                   │
     │  6. Exchange code for tokens          │                   │
     ├──────────────────>├───────────────────>│                   │
     │                   │                   │                   │
     │  7. Set auth cookie & forward request │                   │
     ├──────────────────>├───────────────────────────────────────>│
     │                   │                   │                   │
     │  8. Response      │                   │                   │
     │<──────────────────┴───────────────────────────────────────┤

Accessing User Information (Cognito)

Please see our repo https://github.com/co-cddo/gds-idea-app-auth which automatically validates and verifies tokens to provide you with a user object containing user details.

Security Considerations

NoAuth

  • ⚠️ No access control - Anyone can access your application
  • ✅ Use for truly public content only
  • ✅ Consider WAF rules for rate limiting
  • ✅ Ensure application doesn't expose sensitive data

Cognito

  • OAuth2 standard - Industry-standard authentication
  • Session management - ALB handles session cookies
  • User pool integration - Leverages existing user directory