diff --git a/CODEOWNERS b/CODEOWNERS
index 73110f757fcdbfa586ff0aac5230e094803de223..f4a1d72edc0662663466422d9c76139f9cb81920 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1371,8 +1371,8 @@ build.json @home-assistant/supervisor
 /tests/components/tomorrowio/ @raman325 @lymanepp
 /homeassistant/components/totalconnect/ @austinmroczek
 /tests/components/totalconnect/ @austinmroczek
-/homeassistant/components/tplink/ @rytilahti @thegardenmonkey @bdraco
-/tests/components/tplink/ @rytilahti @thegardenmonkey @bdraco
+/homeassistant/components/tplink/ @rytilahti @thegardenmonkey @bdraco @sdb9696
+/tests/components/tplink/ @rytilahti @thegardenmonkey @bdraco @sdb9696
 /homeassistant/components/tplink_omada/ @MarkGodwin
 /tests/components/tplink_omada/ @MarkGodwin
 /homeassistant/components/traccar/ @ludeeus
diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py
index 4efd7ffdf0bead90cdd2229e67f0c564b48ea0aa..4b684abf280163a93ca92b26057f26629f6a70b9 100644
--- a/homeassistant/components/tplink/__init__.py
+++ b/homeassistant/components/tplink/__init__.py
@@ -3,37 +3,66 @@ from __future__ import annotations
 
 import asyncio
 from datetime import timedelta
+import logging
 from typing import Any
 
-from kasa import SmartDevice, SmartDeviceException
-from kasa.discover import Discover
+from aiohttp import ClientSession
+from kasa import (
+    AuthenticationException,
+    Credentials,
+    DeviceConfig,
+    Discover,
+    SmartDevice,
+    SmartDeviceException,
+)
+from kasa.httpclient import get_cookie_jar
 
 from homeassistant import config_entries
 from homeassistant.components import network
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.const import (
+    CONF_ALIAS,
+    CONF_AUTHENTICATION,
     CONF_HOST,
     CONF_MAC,
-    CONF_NAME,
+    CONF_MODEL,
+    CONF_PASSWORD,
+    CONF_USERNAME,
     EVENT_HOMEASSISTANT_STARTED,
 )
 from homeassistant.core import HomeAssistant, callback
-from homeassistant.exceptions import ConfigEntryNotReady
+from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
 from homeassistant.helpers import (
     config_validation as cv,
     device_registry as dr,
     discovery_flow,
 )
+from homeassistant.helpers.aiohttp_client import async_create_clientsession
 from homeassistant.helpers.event import async_track_time_interval
 from homeassistant.helpers.typing import ConfigType
 
-from .const import DOMAIN, PLATFORMS
+from .const import (
+    CONF_DEVICE_CONFIG,
+    CONNECT_TIMEOUT,
+    DISCOVERY_TIMEOUT,
+    DOMAIN,
+    PLATFORMS,
+)
 from .coordinator import TPLinkDataUpdateCoordinator
 from .models import TPLinkData
 
 DISCOVERY_INTERVAL = timedelta(minutes=15)
 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
 
+_LOGGER = logging.getLogger(__name__)
+
+
+def create_async_tplink_clientsession(hass: HomeAssistant) -> ClientSession:
+    """Return aiohttp clientsession with cookie jar configured."""
+    return async_create_clientsession(
+        hass, verify_ssl=False, cookie_jar=get_cookie_jar()
+    )
+
 
 @callback
 def async_trigger_discovery(
@@ -47,17 +76,31 @@ def async_trigger_discovery(
             DOMAIN,
             context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
             data={
-                CONF_NAME: device.alias,
+                CONF_ALIAS: device.alias or mac_alias(device.mac),
                 CONF_HOST: device.host,
                 CONF_MAC: formatted_mac,
+                CONF_DEVICE_CONFIG: device.config.to_dict(
+                    credentials_hash=device.credentials_hash,
+                    exclude_credentials=True,
+                ),
             },
         )
 
 
 async def async_discover_devices(hass: HomeAssistant) -> dict[str, SmartDevice]:
     """Discover TPLink devices on configured network interfaces."""
+
+    credentials = await get_credentials(hass)
     broadcast_addresses = await network.async_get_ipv4_broadcast_addresses(hass)
-    tasks = [Discover.discover(target=str(address)) for address in broadcast_addresses]
+    tasks = [
+        Discover.discover(
+            target=str(address),
+            discovery_timeout=DISCOVERY_TIMEOUT,
+            timeout=CONNECT_TIMEOUT,
+            credentials=credentials,
+        )
+        for address in broadcast_addresses
+    ]
     discovered_devices: dict[str, SmartDevice] = {}
     for device_list in await asyncio.gather(*tasks):
         for device in device_list.values():
@@ -67,7 +110,7 @@ async def async_discover_devices(hass: HomeAssistant) -> dict[str, SmartDevice]:
 
 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
     """Set up the TP-Link component."""
-    hass.data[DOMAIN] = {}
+    hass.data.setdefault(DOMAIN, {})
 
     if discovered_devices := await async_discover_devices(hass):
         async_trigger_discovery(hass, discovered_devices)
@@ -86,12 +129,51 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
 
 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Set up TPLink from a config entry."""
-    host = entry.data[CONF_HOST]
+    host: str = entry.data[CONF_HOST]
+    credentials = await get_credentials(hass)
+
+    config: DeviceConfig | None = None
+    if config_dict := entry.data.get(CONF_DEVICE_CONFIG):
+        try:
+            config = DeviceConfig.from_dict(config_dict)
+        except SmartDeviceException:
+            _LOGGER.warning(
+                "Invalid connection type dict for %s: %s", host, config_dict
+            )
+
+    if not config:
+        config = DeviceConfig(host)
+
+    config.timeout = CONNECT_TIMEOUT
+    if config.uses_http is True:
+        config.http_client = create_async_tplink_clientsession(hass)
+    if credentials:
+        config.credentials = credentials
     try:
-        device: SmartDevice = await Discover.discover_single(host, timeout=10)
+        device: SmartDevice = await SmartDevice.connect(config=config)
+    except AuthenticationException as ex:
+        raise ConfigEntryAuthFailed from ex
     except SmartDeviceException as ex:
         raise ConfigEntryNotReady from ex
 
+    device_config_dict = device.config.to_dict(
+        credentials_hash=device.credentials_hash, exclude_credentials=True
+    )
+    updates: dict[str, Any] = {}
+    if device_config_dict != config_dict:
+        updates[CONF_DEVICE_CONFIG] = device_config_dict
+    if entry.data.get(CONF_ALIAS) != device.alias:
+        updates[CONF_ALIAS] = device.alias
+    if entry.data.get(CONF_MODEL) != device.model:
+        updates[CONF_MODEL] = device.model
+    if updates:
+        hass.config_entries.async_update_entry(
+            entry,
+            data={
+                **entry.data,
+                **updates,
+            },
+        )
     found_mac = dr.format_mac(device.mac)
     if found_mac != entry.unique_id:
         # If the mac address of the device does not match the unique_id
@@ -130,6 +212,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
         hass_data.pop(entry.entry_id)
     await device.protocol.close()
+
     return unload_ok
 
 
@@ -141,3 +224,25 @@ def legacy_device_id(device: SmartDevice) -> str:
     if "_" not in device_id:
         return device_id
     return device_id.split("_")[1]
+
+
+async def get_credentials(hass: HomeAssistant) -> Credentials | None:
+    """Retrieve the credentials from hass data."""
+    if DOMAIN in hass.data and CONF_AUTHENTICATION in hass.data[DOMAIN]:
+        auth = hass.data[DOMAIN][CONF_AUTHENTICATION]
+        return Credentials(auth[CONF_USERNAME], auth[CONF_PASSWORD])
+
+    return None
+
+
+async def set_credentials(hass: HomeAssistant, username: str, password: str) -> None:
+    """Save the credentials to HASS data."""
+    hass.data.setdefault(DOMAIN, {})[CONF_AUTHENTICATION] = {
+        CONF_USERNAME: username,
+        CONF_PASSWORD: password,
+    }
+
+
+def mac_alias(mac: str) -> str:
+    """Convert a MAC address to a short address for the UI."""
+    return mac.replace(":", "")[-4:].upper()
diff --git a/homeassistant/components/tplink/config_flow.py b/homeassistant/components/tplink/config_flow.py
index a783c7b902fa6b6d2d35dee1b8617b54f0992996..68a40d81415922fb92c912826ba51c52de3102be 100644
--- a/homeassistant/components/tplink/config_flow.py
+++ b/homeassistant/components/tplink/config_flow.py
@@ -1,28 +1,57 @@
 """Config flow for TP-Link."""
 from __future__ import annotations
 
+from collections.abc import Mapping
 from typing import Any
 
-from kasa import SmartDevice, SmartDeviceException
-from kasa.discover import Discover
+from kasa import (
+    AuthenticationException,
+    Credentials,
+    DeviceConfig,
+    Discover,
+    SmartDevice,
+    SmartDeviceException,
+    TimeoutException,
+)
 import voluptuous as vol
 
 from homeassistant import config_entries
 from homeassistant.components import dhcp
-from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MAC
+from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry, ConfigEntryState
+from homeassistant.const import (
+    CONF_ALIAS,
+    CONF_DEVICE,
+    CONF_HOST,
+    CONF_MAC,
+    CONF_MODEL,
+    CONF_PASSWORD,
+    CONF_USERNAME,
+)
 from homeassistant.core import callback
-from homeassistant.data_entry_flow import FlowResult
+from homeassistant.data_entry_flow import AbortFlow, FlowResult
 from homeassistant.helpers import device_registry as dr
 from homeassistant.helpers.typing import DiscoveryInfoType
 
-from . import async_discover_devices
-from .const import DOMAIN
+from . import (
+    async_discover_devices,
+    create_async_tplink_clientsession,
+    get_credentials,
+    mac_alias,
+    set_credentials,
+)
+from .const import CONF_DEVICE_CONFIG, CONNECT_TIMEOUT, DOMAIN
+
+STEP_AUTH_DATA_SCHEMA = vol.Schema(
+    {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
+)
 
 
 class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
     """Handle a config flow for tplink."""
 
     VERSION = 1
+    MINOR_VERSION = 2
+    reauth_entry: ConfigEntry | None = None
 
     def __init__(self) -> None:
         """Initialize the config flow."""
@@ -40,27 +69,114 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
     ) -> FlowResult:
         """Handle integration discovery."""
         return await self._async_handle_discovery(
-            discovery_info[CONF_HOST], discovery_info[CONF_MAC]
+            discovery_info[CONF_HOST],
+            discovery_info[CONF_MAC],
+            discovery_info[CONF_DEVICE_CONFIG],
+        )
+
+    @callback
+    def _update_config_if_entry_in_setup_error(
+        self, entry: ConfigEntry, host: str, config: dict
+    ) -> None:
+        """If discovery encounters a device that is in SETUP_ERROR update the device config."""
+        if entry.state is not ConfigEntryState.SETUP_ERROR:
+            return
+        entry_data = entry.data
+        entry_config_dict = entry_data.get(CONF_DEVICE_CONFIG)
+        if entry_config_dict == config and entry_data[CONF_HOST] == host:
+            return
+        self.hass.config_entries.async_update_entry(
+            entry, data={**entry.data, CONF_DEVICE_CONFIG: config, CONF_HOST: host}
+        )
+        self.hass.async_create_task(
+            self.hass.config_entries.async_reload(entry.entry_id),
+            f"config entry reload {entry.title} {entry.domain} {entry.entry_id}",
         )
+        raise AbortFlow("already_configured")
 
-    async def _async_handle_discovery(self, host: str, mac: str) -> FlowResult:
+    async def _async_handle_discovery(
+        self, host: str, formatted_mac: str, config: dict | None = None
+    ) -> FlowResult:
         """Handle any discovery."""
-        await self.async_set_unique_id(dr.format_mac(mac))
+        current_entry = await self.async_set_unique_id(
+            formatted_mac, raise_on_progress=False
+        )
+        if config and current_entry:
+            self._update_config_if_entry_in_setup_error(current_entry, host, config)
         self._abort_if_unique_id_configured(updates={CONF_HOST: host})
         self._async_abort_entries_match({CONF_HOST: host})
         self.context[CONF_HOST] = host
         for progress in self._async_in_progress():
             if progress.get("context", {}).get(CONF_HOST) == host:
                 return self.async_abort(reason="already_in_progress")
-
+        credentials = await get_credentials(self.hass)
         try:
-            self._discovered_device = await self._async_try_connect(
-                host, raise_on_progress=True
+            await self._async_try_discover_and_update(
+                host, credentials, raise_on_progress=True
             )
+        except AuthenticationException:
+            return await self.async_step_discovery_auth_confirm()
         except SmartDeviceException:
             return self.async_abort(reason="cannot_connect")
+
         return await self.async_step_discovery_confirm()
 
+    async def async_step_discovery_auth_confirm(
+        self, user_input: dict[str, Any] | None = None
+    ) -> FlowResult:
+        """Dialog that informs the user that auth is required."""
+        assert self._discovered_device is not None
+        errors = {}
+
+        credentials = await get_credentials(self.hass)
+        if credentials and credentials != self._discovered_device.config.credentials:
+            try:
+                device = await self._async_try_connect(
+                    self._discovered_device, credentials
+                )
+            except AuthenticationException:
+                pass  # Authentication exceptions should continue to the rest of the step
+            else:
+                self._discovered_device = device
+                return await self.async_step_discovery_confirm()
+
+        if user_input:
+            username = user_input[CONF_USERNAME]
+            password = user_input[CONF_PASSWORD]
+            credentials = Credentials(username, password)
+            try:
+                device = await self._async_try_connect(
+                    self._discovered_device, credentials
+                )
+            except AuthenticationException:
+                errors[CONF_PASSWORD] = "invalid_auth"
+            except SmartDeviceException:
+                errors["base"] = "cannot_connect"
+            else:
+                self._discovered_device = device
+                await set_credentials(self.hass, username, password)
+                self.hass.async_create_task(self._async_reload_requires_auth_entries())
+                return await self.async_step_discovery_confirm()
+
+        placeholders = self._async_make_placeholders_from_discovery()
+        self.context["title_placeholders"] = placeholders
+        return self.async_show_form(
+            step_id="discovery_auth_confirm",
+            data_schema=STEP_AUTH_DATA_SCHEMA,
+            errors=errors,
+            description_placeholders=placeholders,
+        )
+
+    def _async_make_placeholders_from_discovery(self) -> dict[str, str]:
+        """Make placeholders for the discovery steps."""
+        discovered_device = self._discovered_device
+        assert discovered_device is not None
+        return {
+            "name": discovered_device.alias or mac_alias(discovered_device.mac),
+            "model": discovered_device.model,
+            "host": discovered_device.host,
+        }
+
     async def async_step_discovery_confirm(
         self, user_input: dict[str, Any] | None = None
     ) -> FlowResult:
@@ -70,11 +186,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
             return self._async_create_entry_from_device(self._discovered_device)
 
         self._set_confirm_only()
-        placeholders = {
-            "name": self._discovered_device.alias,
-            "model": self._discovered_device.model,
-            "host": self._discovered_device.host,
-        }
+        placeholders = self._async_make_placeholders_from_discovery()
         self.context["title_placeholders"] = placeholders
         return self.async_show_form(
             step_id="discovery_confirm", description_placeholders=placeholders
@@ -88,8 +200,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
         if user_input is not None:
             if not (host := user_input[CONF_HOST]):
                 return await self.async_step_pick_device()
+            self._async_abort_entries_match({CONF_HOST: host})
+            self.context[CONF_HOST] = host
+            credentials = await get_credentials(self.hass)
             try:
-                device = await self._async_try_connect(host, raise_on_progress=False)
+                device = await self._async_try_discover_and_update(
+                    host, credentials, raise_on_progress=False
+                )
+            except AuthenticationException:
+                return await self.async_step_user_auth_confirm()
             except SmartDeviceException:
                 errors["base"] = "cannot_connect"
             else:
@@ -101,6 +220,37 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
             errors=errors,
         )
 
+    async def async_step_user_auth_confirm(
+        self, user_input: dict[str, Any] | None = None
+    ) -> FlowResult:
+        """Dialog that informs the user that auth is required."""
+        errors = {}
+        host = self.context[CONF_HOST]
+        assert self._discovered_device is not None
+        if user_input:
+            username = user_input[CONF_USERNAME]
+            password = user_input[CONF_PASSWORD]
+            credentials = Credentials(username, password)
+            try:
+                device = await self._async_try_connect(
+                    self._discovered_device, credentials
+                )
+            except AuthenticationException:
+                errors[CONF_PASSWORD] = "invalid_auth"
+            except SmartDeviceException:
+                errors["base"] = "cannot_connect"
+            else:
+                await set_credentials(self.hass, username, password)
+                self.hass.async_create_task(self._async_reload_requires_auth_entries())
+                return self._async_create_entry_from_device(device)
+
+        return self.async_show_form(
+            step_id="user_auth_confirm",
+            data_schema=STEP_AUTH_DATA_SCHEMA,
+            errors=errors,
+            description_placeholders={CONF_HOST: host},
+        )
+
     async def async_step_pick_device(
         self, user_input: dict[str, Any] | None = None
     ) -> FlowResult:
@@ -108,7 +258,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
         if user_input is not None:
             mac = user_input[CONF_DEVICE]
             await self.async_set_unique_id(mac, raise_on_progress=False)
-            return self._async_create_entry_from_device(self._discovered_devices[mac])
+            self._discovered_device = self._discovered_devices[mac]
+            host = self._discovered_device.host
+
+            self.context[CONF_HOST] = host
+            credentials = await get_credentials(self.hass)
+
+            try:
+                device = await self._async_try_connect(
+                    self._discovered_device, credentials
+                )
+            except AuthenticationException:
+                return await self.async_step_user_auth_confirm()
+            except SmartDeviceException:
+                return self.async_abort(reason="cannot_connect")
+            return self._async_create_entry_from_device(device)
 
         configured_devices = {
             entry.unique_id for entry in self._async_current_entries()
@@ -116,7 +280,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
         self._discovered_devices = await async_discover_devices(self.hass)
         devices_name = {
             formatted_mac: (
-                f"{device.alias} {device.model} ({device.host}) {formatted_mac}"
+                f"{device.alias or mac_alias(device.mac)} {device.model} ({device.host}) {formatted_mac}"
             )
             for formatted_mac, device in self._discovered_devices.items()
             if formatted_mac not in configured_devices
@@ -129,6 +293,25 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
             data_schema=vol.Schema({vol.Required(CONF_DEVICE): vol.In(devices_name)}),
         )
 
+    async def _async_reload_requires_auth_entries(self) -> None:
+        """Reload any in progress config flow that now have credentials."""
+        _config_entries = self.hass.config_entries
+
+        if reauth_entry := self.reauth_entry:
+            await _config_entries.async_reload(reauth_entry.entry_id)
+
+        for flow in _config_entries.flow.async_progress_by_handler(
+            DOMAIN, include_uninitialized=True
+        ):
+            context: dict[str, Any] = flow["context"]
+            if context.get("source") != SOURCE_REAUTH:
+                continue
+            entry_id: str = context["entry_id"]
+            if entry := _config_entries.async_get_entry(entry_id):
+                await _config_entries.async_reload(entry.entry_id)
+                if entry.state is ConfigEntryState.LOADED:
+                    _config_entries.flow.async_abort(flow["flow_id"])
+
     @callback
     def _async_create_entry_from_device(self, device: SmartDevice) -> FlowResult:
         """Create a config entry from a smart device."""
@@ -137,16 +320,113 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
             title=f"{device.alias} {device.model}",
             data={
                 CONF_HOST: device.host,
+                CONF_ALIAS: device.alias,
+                CONF_MODEL: device.model,
+                CONF_DEVICE_CONFIG: device.config.to_dict(
+                    credentials_hash=device.credentials_hash,
+                    exclude_credentials=True,
+                ),
             },
         )
 
+    async def _async_try_discover_and_update(
+        self,
+        host: str,
+        credentials: Credentials | None,
+        raise_on_progress: bool,
+    ) -> SmartDevice:
+        """Try to discover the device and call update.
+
+        Will try to connect to legacy devices if discovery fails.
+        """
+        try:
+            self._discovered_device = await Discover.discover_single(
+                host, credentials=credentials
+            )
+        except TimeoutException:
+            # Try connect() to legacy devices if discovery fails
+            self._discovered_device = await SmartDevice.connect(
+                config=DeviceConfig(host)
+            )
+        else:
+            if self._discovered_device.config.uses_http:
+                self._discovered_device.config.http_client = (
+                    create_async_tplink_clientsession(self.hass)
+                )
+            await self._discovered_device.update()
+        await self.async_set_unique_id(
+            dr.format_mac(self._discovered_device.mac),
+            raise_on_progress=raise_on_progress,
+        )
+        return self._discovered_device
+
     async def _async_try_connect(
-        self, host: str, raise_on_progress: bool = True
+        self,
+        discovered_device: SmartDevice,
+        credentials: Credentials | None,
     ) -> SmartDevice:
         """Try to connect."""
-        self._async_abort_entries_match({CONF_HOST: host})
-        device: SmartDevice = await Discover.discover_single(host)
+        self._async_abort_entries_match({CONF_HOST: discovered_device.host})
+
+        config = discovered_device.config
+        if credentials:
+            config.credentials = credentials
+        config.timeout = CONNECT_TIMEOUT
+        if config.uses_http:
+            config.http_client = create_async_tplink_clientsession(self.hass)
+
+        self._discovered_device = await SmartDevice.connect(config=config)
         await self.async_set_unique_id(
-            dr.format_mac(device.mac), raise_on_progress=raise_on_progress
+            dr.format_mac(self._discovered_device.mac),
+            raise_on_progress=False,
+        )
+        return self._discovered_device
+
+    async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
+        """Start the reauthentication flow if the device needs updated credentials."""
+        self.reauth_entry = self.hass.config_entries.async_get_entry(
+            self.context["entry_id"]
+        )
+        return await self.async_step_reauth_confirm()
+
+    async def async_step_reauth_confirm(
+        self, user_input: dict[str, Any] | None = None
+    ) -> FlowResult:
+        """Dialog that informs the user that reauth is required."""
+        errors: dict[str, str] = {}
+        reauth_entry = self.reauth_entry
+        assert reauth_entry is not None
+        entry_data = reauth_entry.data
+        host = entry_data[CONF_HOST]
+
+        if user_input:
+            username = user_input[CONF_USERNAME]
+            password = user_input[CONF_PASSWORD]
+            credentials = Credentials(username, password)
+            try:
+                await self._async_try_discover_and_update(
+                    host,
+                    credentials=credentials,
+                    raise_on_progress=True,
+                )
+            except AuthenticationException:
+                errors[CONF_PASSWORD] = "invalid_auth"
+            except SmartDeviceException:
+                errors["base"] = "cannot_connect"
+            else:
+                await set_credentials(self.hass, username, password)
+                self.hass.async_create_task(self._async_reload_requires_auth_entries())
+                return self.async_abort(reason="reauth_successful")
+
+        # Old config entries will not have these values.
+        alias = entry_data.get(CONF_ALIAS) or "unknown"
+        model = entry_data.get(CONF_MODEL) or "unknown"
+
+        placeholders = {"name": alias, "model": model, "host": host}
+        self.context["title_placeholders"] = placeholders
+        return self.async_show_form(
+            step_id="reauth_confirm",
+            data_schema=STEP_AUTH_DATA_SCHEMA,
+            errors=errors,
+            description_placeholders=placeholders,
         )
-        return device
diff --git a/homeassistant/components/tplink/const.py b/homeassistant/components/tplink/const.py
index 22b5741fceb615b2ab8cdce747e6cf316303e1a4..57047af8092d84239865e5b2adf9d7c9f52bc30d 100644
--- a/homeassistant/components/tplink/const.py
+++ b/homeassistant/components/tplink/const.py
@@ -7,15 +7,14 @@ from homeassistant.const import Platform
 
 DOMAIN = "tplink"
 
+DISCOVERY_TIMEOUT = 5  # Home Assistant will complain if startup takes > 10s
+CONNECT_TIMEOUT = 5
+
 ATTR_CURRENT_A: Final = "current_a"
 ATTR_CURRENT_POWER_W: Final = "current_power_w"
 ATTR_TODAY_ENERGY_KWH: Final = "today_energy_kwh"
 ATTR_TOTAL_ENERGY_KWH: Final = "total_energy_kwh"
 
-CONF_DIMMER: Final = "dimmer"
-CONF_LIGHT: Final = "light"
-CONF_STRIP: Final = "strip"
-CONF_SWITCH: Final = "switch"
-CONF_SENSOR: Final = "sensor"
+CONF_DEVICE_CONFIG: Final = "device_config"
 
 PLATFORMS: Final = [Platform.LIGHT, Platform.SENSOR, Platform.SWITCH]
diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json
index 162344f04ec20b0c2f8a3e3fd1be3f48b38677bf..5791e429d71c0a8efc3e4ecb09cfc90cb5b66ffc 100644
--- a/homeassistant/components/tplink/manifest.json
+++ b/homeassistant/components/tplink/manifest.json
@@ -1,13 +1,17 @@
 {
   "domain": "tplink",
-  "name": "TP-Link Kasa Smart",
-  "codeowners": ["@rytilahti", "@thegardenmonkey", "@bdraco"],
+  "name": "TP-Link Smart Home",
+  "codeowners": ["@rytilahti", "@thegardenmonkey", "@bdraco", "@sdb9696"],
   "config_flow": true,
   "dependencies": ["network"],
   "dhcp": [
     {
       "registered_devices": true
     },
+    {
+      "hostname": "e[sp]*",
+      "macaddress": "3C52A1*"
+    },
     {
       "hostname": "e[sp]*",
       "macaddress": "54AF97*"
@@ -32,6 +36,10 @@
       "hostname": "hs*",
       "macaddress": "9C5322*"
     },
+    {
+      "hostname": "k[lps]*",
+      "macaddress": "5091E3*"
+    },
     {
       "hostname": "k[lps]*",
       "macaddress": "9C5322*"
@@ -163,11 +171,31 @@
     {
       "hostname": "k[lps]*",
       "macaddress": "1C61B4*"
+    },
+    {
+      "hostname": "l5*",
+      "macaddress": "5CE931*"
+    },
+    {
+      "hostname": "p1*",
+      "macaddress": "482254*"
+    },
+    {
+      "hostname": "p1*",
+      "macaddress": "30DE4B*"
+    },
+    {
+      "hostname": "l9*",
+      "macaddress": "A842A1*"
+    },
+    {
+      "hostname": "l9*",
+      "macaddress": "3460F9*"
     }
   ],
   "documentation": "https://www.home-assistant.io/integrations/tplink",
   "iot_class": "local_polling",
   "loggers": ["kasa"],
   "quality_scale": "platinum",
-  "requirements": ["python-kasa[speedups]==0.5.4"]
+  "requirements": ["python-kasa[speedups]==0.6.0.1"]
 }
diff --git a/homeassistant/components/tplink/strings.json b/homeassistant/components/tplink/strings.json
index 3b4024c07b414075f039b5a718eed7d33e93314d..3c4711d16321a0fc775e3205d018c84844e1d7e8 100644
--- a/homeassistant/components/tplink/strings.json
+++ b/homeassistant/components/tplink/strings.json
@@ -18,6 +18,34 @@
       },
       "discovery_confirm": {
         "description": "Do you want to set up {name} {model} ({host})?"
+      },
+      "user_auth_confirm": {
+        "title": "Authenticate",
+        "description": "The device requires authentication, please input your credentials below.",
+        "data": {
+          "username": "[%key:common::config_flow::data::username%]",
+          "password": "[%key:common::config_flow::data::password%]"
+        }
+      },
+      "discovery_auth_confirm": {
+        "title": "Authenticate",
+        "description": "The device requires authentication, please input your credentials below.",
+        "data": {
+          "username": "[%key:common::config_flow::data::username%]",
+          "password": "[%key:common::config_flow::data::password%]"
+        }
+      },
+      "reauth": {
+        "title": "[%key:common::config_flow::title::reauth%]",
+        "description": "The device needs updated credentials, please input your credentials below."
+      },
+      "reauth_confirm": {
+        "title": "[%key:common::config_flow::title::reauth%]",
+        "description": "The device needs updated credentials, please input your credentials below.",
+        "data": {
+          "username": "[%key:common::config_flow::data::username%]",
+          "password": "[%key:common::config_flow::data::password%]"
+        }
       }
     },
     "error": {
@@ -25,7 +53,8 @@
     },
     "abort": {
       "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
-      "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
+      "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
+      "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
     }
   },
   "entity": {
diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py
index b1ca848260f040598559ddfe80f52c214dbb7eae..9a54a952666a90f21d61f2d088a7d27ff5a03851 100644
--- a/homeassistant/components/tplink/switch.py
+++ b/homeassistant/components/tplink/switch.py
@@ -41,7 +41,9 @@ async def async_setup_entry(
     elif device.is_plug:
         entities.append(SmartPlugSwitch(device, parent_coordinator))
 
-    entities.append(SmartPlugLedSwitch(device, parent_coordinator))
+    # this will be removed on the led is implemented
+    if hasattr(device, "led"):
+        entities.append(SmartPlugLedSwitch(device, parent_coordinator))
 
     async_add_entities(entities)
 
@@ -86,7 +88,7 @@ class SmartPlugLedSwitch(CoordinatedTPLinkEntity, SwitchEntity):
 class SmartPlugSwitch(CoordinatedTPLinkEntity, SwitchEntity):
     """Representation of a TPLink Smart Plug switch."""
 
-    _attr_name = None
+    _attr_name: str | None = None
 
     def __init__(
         self,
diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py
index 33d069c5663ed9dcd3ea86695e424c1b9cf793d8..a63c814d5985ad54d76a8cebf0f81c4c75e38984 100644
--- a/homeassistant/generated/dhcp.py
+++ b/homeassistant/generated/dhcp.py
@@ -603,6 +603,11 @@ DHCP: list[dict[str, str | bool]] = [
         "domain": "tplink",
         "registered_devices": True,
     },
+    {
+        "domain": "tplink",
+        "hostname": "e[sp]*",
+        "macaddress": "3C52A1*",
+    },
     {
         "domain": "tplink",
         "hostname": "e[sp]*",
@@ -633,6 +638,11 @@ DHCP: list[dict[str, str | bool]] = [
         "hostname": "hs*",
         "macaddress": "9C5322*",
     },
+    {
+        "domain": "tplink",
+        "hostname": "k[lps]*",
+        "macaddress": "5091E3*",
+    },
     {
         "domain": "tplink",
         "hostname": "k[lps]*",
@@ -798,6 +808,31 @@ DHCP: list[dict[str, str | bool]] = [
         "hostname": "k[lps]*",
         "macaddress": "1C61B4*",
     },
+    {
+        "domain": "tplink",
+        "hostname": "l5*",
+        "macaddress": "5CE931*",
+    },
+    {
+        "domain": "tplink",
+        "hostname": "p1*",
+        "macaddress": "482254*",
+    },
+    {
+        "domain": "tplink",
+        "hostname": "p1*",
+        "macaddress": "30DE4B*",
+    },
+    {
+        "domain": "tplink",
+        "hostname": "l9*",
+        "macaddress": "A842A1*",
+    },
+    {
+        "domain": "tplink",
+        "hostname": "l9*",
+        "macaddress": "3460F9*",
+    },
     {
         "domain": "tuya",
         "macaddress": "105A17*",
diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json
index 2aa315a2daf4c6836f363e2a23e1dacc03dbc961..1cb43016efcb12d5788801f7b70c654260be7e42 100644
--- a/homeassistant/generated/integrations.json
+++ b/homeassistant/generated/integrations.json
@@ -6086,7 +6086,7 @@
           "integration_type": "hub",
           "config_flow": true,
           "iot_class": "local_polling",
-          "name": "TP-Link Kasa Smart"
+          "name": "TP-Link Smart Home"
         },
         "tplink_omada": {
           "integration_type": "hub",
diff --git a/requirements_all.txt b/requirements_all.txt
index 8ec8525e36c533eac31e57f1c32f6aa79307b04f..cd43f8a13394cbbb9541ca58f4877590c7eda935 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2213,7 +2213,7 @@ python-join-api==0.0.9
 python-juicenet==1.1.0
 
 # homeassistant.components.tplink
-python-kasa[speedups]==0.5.4
+python-kasa[speedups]==0.6.0.1
 
 # homeassistant.components.lirc
 # python-lirc==1.2.3
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 8d38f2703227ecafaa11c083657f167e1202f433..a2618222da8aeec833eb66ac8b7a9ed71f1b2d10 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -1683,7 +1683,7 @@ python-izone==1.2.9
 python-juicenet==1.1.0
 
 # homeassistant.components.tplink
-python-kasa[speedups]==0.5.4
+python-kasa[speedups]==0.6.0.1
 
 # homeassistant.components.matter
 python-matter-server==5.1.1
diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py
index 9006a058c5784599532f0e487e642969c9e57d66..4a79f39f6a7bd95d337768e225f594114fc14171 100644
--- a/tests/components/tplink/__init__.py
+++ b/tests/components/tplink/__init__.py
@@ -3,6 +3,10 @@
 from unittest.mock import AsyncMock, MagicMock, patch
 
 from kasa import (
+    ConnectionType,
+    DeviceConfig,
+    DeviceFamilyType,
+    EncryptType,
     SmartBulb,
     SmartDevice,
     SmartDimmer,
@@ -13,7 +17,13 @@ from kasa import (
 from kasa.exceptions import SmartDeviceException
 from kasa.protocol import TPLinkSmartHomeProtocol
 
-from homeassistant.components.tplink import CONF_HOST
+from homeassistant.components.tplink import (
+    CONF_ALIAS,
+    CONF_DEVICE_CONFIG,
+    CONF_HOST,
+    CONF_MODEL,
+    Credentials,
+)
 from homeassistant.components.tplink.const import DOMAIN
 from homeassistant.core import HomeAssistant
 
@@ -22,10 +32,61 @@ from tests.common import MockConfigEntry
 MODULE = "homeassistant.components.tplink"
 MODULE_CONFIG_FLOW = "homeassistant.components.tplink.config_flow"
 IP_ADDRESS = "127.0.0.1"
+IP_ADDRESS2 = "127.0.0.2"
 ALIAS = "My Bulb"
 MODEL = "HS100"
 MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
+MAC_ADDRESS2 = "11:22:33:44:55:66"
 DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
+CREDENTIALS_HASH_LEGACY = ""
+DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS)
+DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(
+    credentials_hash=CREDENTIALS_HASH_LEGACY, exclude_credentials=True
+)
+CREDENTIALS = Credentials("foo", "bar")
+CREDENTIALS_HASH_AUTH = "abcdefghijklmnopqrstuv=="
+DEVICE_CONFIG_AUTH = DeviceConfig(
+    IP_ADDRESS,
+    credentials=CREDENTIALS,
+    connection_type=ConnectionType(
+        DeviceFamilyType.IotSmartPlugSwitch, EncryptType.Klap
+    ),
+    uses_http=True,
+)
+DEVICE_CONFIG_AUTH2 = DeviceConfig(
+    IP_ADDRESS2,
+    credentials=CREDENTIALS,
+    connection_type=ConnectionType(
+        DeviceFamilyType.IotSmartPlugSwitch, EncryptType.Klap
+    ),
+    uses_http=True,
+)
+DEVICE_CONFIG_DICT_AUTH = DEVICE_CONFIG_AUTH.to_dict(
+    credentials_hash=CREDENTIALS_HASH_AUTH, exclude_credentials=True
+)
+DEVICE_CONFIG_DICT_AUTH2 = DEVICE_CONFIG_AUTH2.to_dict(
+    credentials_hash=CREDENTIALS_HASH_AUTH, exclude_credentials=True
+)
+
+CREATE_ENTRY_DATA_LEGACY = {
+    CONF_HOST: IP_ADDRESS,
+    CONF_ALIAS: ALIAS,
+    CONF_MODEL: MODEL,
+    CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
+}
+
+CREATE_ENTRY_DATA_AUTH = {
+    CONF_HOST: IP_ADDRESS,
+    CONF_ALIAS: ALIAS,
+    CONF_MODEL: MODEL,
+    CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+}
+CREATE_ENTRY_DATA_AUTH2 = {
+    CONF_HOST: IP_ADDRESS2,
+    CONF_ALIAS: ALIAS,
+    CONF_MODEL: MODEL,
+    CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH2,
+}
 
 
 def _mock_protocol() -> TPLinkSmartHomeProtocol:
@@ -34,11 +95,16 @@ def _mock_protocol() -> TPLinkSmartHomeProtocol:
     return protocol
 
 
-def _mocked_bulb() -> SmartBulb:
+def _mocked_bulb(
+    device_config=DEVICE_CONFIG_LEGACY,
+    credentials_hash=CREDENTIALS_HASH_LEGACY,
+    mac=MAC_ADDRESS,
+    alias=ALIAS,
+) -> SmartBulb:
     bulb = MagicMock(auto_spec=SmartBulb, name="Mocked bulb")
     bulb.update = AsyncMock()
-    bulb.mac = MAC_ADDRESS
-    bulb.alias = ALIAS
+    bulb.mac = mac
+    bulb.alias = alias
     bulb.model = MODEL
     bulb.host = IP_ADDRESS
     bulb.brightness = 50
@@ -52,7 +118,7 @@ def _mocked_bulb() -> SmartBulb:
     bulb.effect = None
     bulb.effect_list = None
     bulb.hsv = (10, 30, 5)
-    bulb.device_id = MAC_ADDRESS
+    bulb.device_id = mac
     bulb.valid_temperature_range.min = 4000
     bulb.valid_temperature_range.max = 9000
     bulb.hw_info = {"sw_ver": "1.0.0", "hw_ver": "1.0.0"}
@@ -62,6 +128,8 @@ def _mocked_bulb() -> SmartBulb:
     bulb.set_hsv = AsyncMock()
     bulb.set_color_temp = AsyncMock()
     bulb.protocol = _mock_protocol()
+    bulb.config = device_config
+    bulb.credentials_hash = credentials_hash
     return bulb
 
 
@@ -103,6 +171,8 @@ def _mocked_smart_light_strip() -> SmartLightStrip:
     strip.set_effect = AsyncMock()
     strip.set_custom_effect = AsyncMock()
     strip.protocol = _mock_protocol()
+    strip.config = DEVICE_CONFIG_LEGACY
+    strip.credentials_hash = CREDENTIALS_HASH_LEGACY
     return strip
 
 
@@ -134,6 +204,8 @@ def _mocked_dimmer() -> SmartDimmer:
     dimmer.set_color_temp = AsyncMock()
     dimmer.set_led = AsyncMock()
     dimmer.protocol = _mock_protocol()
+    dimmer.config = DEVICE_CONFIG_LEGACY
+    dimmer.credentials_hash = CREDENTIALS_HASH_LEGACY
     return dimmer
 
 
@@ -155,6 +227,8 @@ def _mocked_plug() -> SmartPlug:
     plug.turn_on = AsyncMock()
     plug.set_led = AsyncMock()
     plug.protocol = _mock_protocol()
+    plug.config = DEVICE_CONFIG_LEGACY
+    plug.credentials_hash = CREDENTIALS_HASH_LEGACY
     return plug
 
 
@@ -176,6 +250,8 @@ def _mocked_strip() -> SmartStrip:
     strip.turn_on = AsyncMock()
     strip.set_led = AsyncMock()
     strip.protocol = _mock_protocol()
+    strip.config = DEVICE_CONFIG_LEGACY
+    strip.credentials_hash = CREDENTIALS_HASH_LEGACY
     plug0 = _mocked_plug()
     plug0.alias = "Plug0"
     plug0.device_id = "bb:bb:cc:dd:ee:ff_PLUG0DEVICEID"
@@ -212,6 +288,15 @@ def _patch_single_discovery(device=None, no_device=False):
     )
 
 
+def _patch_connect(device=None, no_device=False):
+    async def _connect(*args, **kwargs):
+        if no_device:
+            raise SmartDeviceException
+        return device if device else _mocked_bulb()
+
+    return patch("homeassistant.components.tplink.SmartDevice.connect", new=_connect)
+
+
 async def initialize_config_entry_for_device(
     hass: HomeAssistant, dev: SmartDevice
 ) -> MockConfigEntry:
@@ -225,7 +310,9 @@ async def initialize_config_entry_for_device(
     )
     config_entry.add_to_hass(hass)
 
-    with _patch_discovery(device=dev), _patch_single_discovery(device=dev):
+    with _patch_discovery(device=dev), _patch_single_discovery(
+        device=dev
+    ), _patch_connect(device=dev):
         await hass.config_entries.async_setup(config_entry.entry_id)
         await hass.async_block_till_done()
 
diff --git a/tests/components/tplink/conftest.py b/tests/components/tplink/conftest.py
index 20ce09b9ec8533fff6b8bf3342ccdd146802289c..7e7e6961b91adc9f5684201e013995723b3e4a06 100644
--- a/tests/components/tplink/conftest.py
+++ b/tests/components/tplink/conftest.py
@@ -1,18 +1,75 @@
 """tplink conftest."""
 
+from collections.abc import Generator
+import copy
+from unittest.mock import DEFAULT, AsyncMock, patch
+
 import pytest
 
-from . import _patch_discovery
+from homeassistant.components.tplink import DOMAIN
+from homeassistant.core import HomeAssistant
+
+from . import (
+    CREATE_ENTRY_DATA_LEGACY,
+    CREDENTIALS_HASH_AUTH,
+    DEVICE_CONFIG_AUTH,
+    IP_ADDRESS,
+    IP_ADDRESS2,
+    MAC_ADDRESS,
+    MAC_ADDRESS2,
+    _mocked_bulb,
+)
 
-from tests.common import mock_device_registry, mock_registry
+from tests.common import MockConfigEntry, mock_device_registry, mock_registry
 
 
 @pytest.fixture
 def mock_discovery():
     """Mock python-kasa discovery."""
-    with _patch_discovery() as mock_discover:
-        mock_discover.return_value = {}
-        yield mock_discover
+    with patch.multiple(
+        "homeassistant.components.tplink.Discover",
+        discover=DEFAULT,
+        discover_single=DEFAULT,
+    ) as mock_discovery:
+        device = _mocked_bulb(
+            device_config=copy.deepcopy(DEVICE_CONFIG_AUTH),
+            credentials_hash=CREDENTIALS_HASH_AUTH,
+            alias=None,
+        )
+        devices = {
+            "127.0.0.1": _mocked_bulb(
+                device_config=copy.deepcopy(DEVICE_CONFIG_AUTH),
+                credentials_hash=CREDENTIALS_HASH_AUTH,
+                alias=None,
+            )
+        }
+        mock_discovery["discover"].return_value = devices
+        mock_discovery["discover_single"].return_value = device
+        mock_discovery["mock_device"] = device
+        yield mock_discovery
+
+
+@pytest.fixture
+def mock_connect():
+    """Mock python-kasa connect."""
+    with patch("homeassistant.components.tplink.SmartDevice.connect") as mock_connect:
+        devices = {
+            IP_ADDRESS: _mocked_bulb(
+                device_config=DEVICE_CONFIG_AUTH, credentials_hash=CREDENTIALS_HASH_AUTH
+            ),
+            IP_ADDRESS2: _mocked_bulb(
+                device_config=DEVICE_CONFIG_AUTH,
+                credentials_hash=CREDENTIALS_HASH_AUTH,
+                mac=MAC_ADDRESS2,
+            ),
+        }
+
+        def get_device(config):
+            nonlocal devices
+            return devices[config.host]
+
+        mock_connect.side_effect = get_device
+        yield {"connect": mock_connect, "mock_devices": devices}
 
 
 @pytest.fixture(name="device_reg")
@@ -30,3 +87,55 @@ def entity_reg_fixture(hass):
 @pytest.fixture(autouse=True)
 def tplink_mock_get_source_ip(mock_get_source_ip):
     """Mock network util's async_get_source_ip."""
+
+
+@pytest.fixture
+def mock_setup_entry() -> Generator[AsyncMock, None, None]:
+    """Override async_setup_entry."""
+    with patch.multiple(
+        async_setup=DEFAULT,
+        async_setup_entry=DEFAULT,
+    ) as mock_setup_entry:
+        mock_setup_entry["async_setup"].return_value = True
+        mock_setup_entry["async_setup_entry"].return_value = True
+        yield mock_setup_entry
+
+
+@pytest.fixture
+def mock_init() -> Generator[AsyncMock, None, None]:
+    """Override async_setup_entry."""
+    with patch.multiple(
+        "homeassistant.components.tplink",
+        async_setup=DEFAULT,
+        async_setup_entry=DEFAULT,
+        async_unload_entry=DEFAULT,
+    ) as mock_init:
+        mock_init["async_setup"].return_value = True
+        mock_init["async_setup_entry"].return_value = True
+        mock_init["async_unload_entry"].return_value = True
+        yield mock_init
+
+
+@pytest.fixture
+def mock_config_entry() -> MockConfigEntry:
+    """Mock ConfigEntry."""
+    return MockConfigEntry(
+        title="TPLink",
+        domain=DOMAIN,
+        data={**CREATE_ENTRY_DATA_LEGACY},
+        unique_id=MAC_ADDRESS,
+    )
+
+
+@pytest.fixture
+async def mock_added_config_entry(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_init,
+) -> MockConfigEntry:
+    """Mock ConfigEntry that's been added to HA."""
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+    assert DOMAIN in hass.config_entries.async_domains()
+    return mock_config_entry
diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py
index 65be41a5655251a83a02dbacbf9b4cfbd056fb84..96cfbead5e453e6684f230df65b9babbdfe30784 100644
--- a/tests/components/tplink/test_config_flow.py
+++ b/tests/components/tplink/test_config_flow.py
@@ -1,21 +1,42 @@
 """Test the tplink config flow."""
-from unittest.mock import patch
+from unittest.mock import AsyncMock, patch
 
+from kasa import TimeoutException
 import pytest
 
 from homeassistant import config_entries
 from homeassistant.components import dhcp
-from homeassistant.components.tplink import DOMAIN
-from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME
+from homeassistant.components.tplink import (
+    DOMAIN,
+    AuthenticationException,
+    Credentials,
+    SmartDeviceException,
+)
+from homeassistant.components.tplink.const import CONF_DEVICE_CONFIG
+from homeassistant.const import (
+    CONF_ALIAS,
+    CONF_DEVICE,
+    CONF_HOST,
+    CONF_MAC,
+    CONF_PASSWORD,
+    CONF_USERNAME,
+)
 from homeassistant.core import HomeAssistant
 from homeassistant.data_entry_flow import FlowResultType
 
 from . import (
     ALIAS,
+    CREATE_ENTRY_DATA_AUTH,
+    CREATE_ENTRY_DATA_AUTH2,
+    CREATE_ENTRY_DATA_LEGACY,
     DEFAULT_ENTRY_TITLE,
+    DEVICE_CONFIG_DICT_AUTH,
+    DEVICE_CONFIG_DICT_LEGACY,
     IP_ADDRESS,
     MAC_ADDRESS,
+    MAC_ADDRESS2,
     MODULE,
+    _patch_connect,
     _patch_discovery,
     _patch_single_discovery,
 )
@@ -25,7 +46,7 @@ from tests.common import MockConfigEntry
 
 async def test_discovery(hass: HomeAssistant) -> None:
     """Test setting up discovery."""
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result = await hass.config_entries.flow.async_init(
             DOMAIN, context={"source": config_entries.SOURCE_USER}
         )
@@ -54,7 +75,7 @@ async def test_discovery(hass: HomeAssistant) -> None:
         assert result2["step_id"] == "pick_device"
         assert not result2["errors"]
 
-    with _patch_discovery(), _patch_single_discovery(), patch(
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch(
         f"{MODULE}.async_setup", return_value=True
     ) as mock_setup, patch(
         f"{MODULE}.async_setup_entry", return_value=True
@@ -67,7 +88,7 @@ async def test_discovery(hass: HomeAssistant) -> None:
 
     assert result3["type"] == "create_entry"
     assert result3["title"] == DEFAULT_ENTRY_TITLE
-    assert result3["data"] == {CONF_HOST: IP_ADDRESS}
+    assert result3["data"] == CREATE_ENTRY_DATA_LEGACY
     mock_setup.assert_called_once()
     mock_setup_entry.assert_called_once()
 
@@ -75,18 +96,244 @@ async def test_discovery(hass: HomeAssistant) -> None:
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
-    assert result["type"] == "form"
+    assert result["type"] == FlowResultType.FORM
     assert result["step_id"] == "user"
     assert not result["errors"]
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
         await hass.async_block_till_done()
 
-    assert result2["type"] == "abort"
+    assert result2["type"] == FlowResultType.ABORT
     assert result2["reason"] == "no_devices_found"
 
 
+async def test_discovery_auth(
+    hass: HomeAssistant, mock_discovery: AsyncMock, mock_connect: AsyncMock, mock_init
+) -> None:
+    """Test authenticated discovery."""
+
+    mock_discovery["mock_device"].update.side_effect = AuthenticationException
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
+        data={
+            CONF_HOST: IP_ADDRESS,
+            CONF_MAC: MAC_ADDRESS,
+            CONF_ALIAS: ALIAS,
+            CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+        },
+    )
+    await hass.async_block_till_done()
+    assert result["type"] == "form"
+    assert result["step_id"] == "discovery_auth_confirm"
+    assert not result["errors"]
+
+    mock_discovery["mock_device"].update.reset_mock(side_effect=True)
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+
+    assert result2["type"] == "form"
+    assert result2["step_id"] == "discovery_confirm"
+    assert not result2["errors"]
+
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"], user_input={}
+    )
+
+    await hass.async_block_till_done()
+    assert result3["type"] is FlowResultType.CREATE_ENTRY
+    assert result3["title"] == DEFAULT_ENTRY_TITLE
+    assert result3["data"] == CREATE_ENTRY_DATA_AUTH
+
+
+@pytest.mark.parametrize(
+    ("error_type", "errors_msg", "error_placement"),
+    [
+        (AuthenticationException, "invalid_auth", CONF_PASSWORD),
+        (SmartDeviceException, "cannot_connect", "base"),
+    ],
+    ids=["invalid-auth", "unknown-error"],
+)
+async def test_discovery_auth_errors(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    mock_init,
+    error_type,
+    errors_msg,
+    error_placement,
+) -> None:
+    """Test handling of discovery authentication errors."""
+    mock_discovery["mock_device"].update.side_effect = AuthenticationException
+    default_connect_side_effect = mock_connect["connect"].side_effect
+    mock_connect["connect"].side_effect = error_type
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
+        data={
+            CONF_HOST: IP_ADDRESS,
+            CONF_MAC: MAC_ADDRESS,
+            CONF_ALIAS: ALIAS,
+            CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+        },
+    )
+    await hass.async_block_till_done()
+    assert result["type"] == "form"
+    assert result["step_id"] == "discovery_auth_confirm"
+    assert not result["errors"]
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["errors"] == {error_placement: errors_msg}
+
+    await hass.async_block_till_done()
+
+    mock_connect["connect"].side_effect = default_connect_side_effect
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"],
+        {
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    assert result3["type"] is FlowResultType.FORM
+    assert result3["step_id"] == "discovery_confirm"
+
+    await hass.async_block_till_done()
+
+    result4 = await hass.config_entries.flow.async_configure(
+        result3["flow_id"],
+        {},
+    )
+    assert result4["type"] is FlowResultType.CREATE_ENTRY
+    assert result4["data"] == CREATE_ENTRY_DATA_AUTH
+
+
+async def test_discovery_new_credentials(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    mock_init,
+) -> None:
+    """Test setting up discovery with new credentials."""
+    mock_discovery["mock_device"].update.side_effect = AuthenticationException
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
+        data={
+            CONF_HOST: IP_ADDRESS,
+            CONF_MAC: MAC_ADDRESS,
+            CONF_ALIAS: ALIAS,
+            CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+        },
+    )
+    await hass.async_block_till_done()
+    assert result["type"] == "form"
+    assert result["step_id"] == "discovery_auth_confirm"
+    assert not result["errors"]
+
+    assert mock_connect["connect"].call_count == 0
+
+    with patch(
+        "homeassistant.components.tplink.config_flow.get_credentials",
+        return_value=Credentials("fake_user", "fake_pass"),
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+        )
+
+    assert mock_connect["connect"].call_count == 1
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["step_id"] == "discovery_confirm"
+
+    await hass.async_block_till_done()
+
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"],
+        {},
+    )
+    assert result3["type"] is FlowResultType.CREATE_ENTRY
+    assert result3["data"] == CREATE_ENTRY_DATA_AUTH
+
+
+async def test_discovery_new_credentials_invalid(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    mock_init,
+) -> None:
+    """Test setting up discovery with new invalid credentials."""
+    mock_discovery["mock_device"].update.side_effect = AuthenticationException
+    default_connect_side_effect = mock_connect["connect"].side_effect
+
+    mock_connect["connect"].side_effect = AuthenticationException
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
+        data={
+            CONF_HOST: IP_ADDRESS,
+            CONF_MAC: MAC_ADDRESS,
+            CONF_ALIAS: ALIAS,
+            CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+        },
+    )
+    await hass.async_block_till_done()
+    assert result["type"] == "form"
+    assert result["step_id"] == "discovery_auth_confirm"
+    assert not result["errors"]
+
+    assert mock_connect["connect"].call_count == 0
+
+    with patch(
+        "homeassistant.components.tplink.config_flow.get_credentials",
+        return_value=Credentials("fake_user", "fake_pass"),
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+        )
+
+    assert mock_connect["connect"].call_count == 1
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["step_id"] == "discovery_auth_confirm"
+
+    await hass.async_block_till_done()
+
+    mock_connect["connect"].side_effect = default_connect_side_effect
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"],
+        {
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    assert result3["type"] is FlowResultType.FORM
+    assert result3["step_id"] == "discovery_confirm"
+
+    result4 = await hass.config_entries.flow.async_configure(
+        result3["flow_id"],
+        {},
+    )
+    assert result4["type"] is FlowResultType.CREATE_ENTRY
+    assert result4["data"] == CREATE_ENTRY_DATA_AUTH
+
+
 async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> None:
     """Test setting up discovery."""
     config_entry = MockConfigEntry(
@@ -94,22 +341,24 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No
     )
     config_entry.add_to_hass(hass)
 
-    with _patch_discovery(), _patch_single_discovery(no_device=True):
+    with _patch_discovery(), _patch_single_discovery(no_device=True), _patch_connect(
+        no_device=True
+    ):
         await hass.config_entries.async_setup(config_entry.entry_id)
         await hass.async_block_till_done()
 
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
-    assert result["type"] == "form"
+    assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "user"
     assert not result["errors"]
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
         await hass.async_block_till_done()
 
-    assert result2["type"] == "form"
+    assert result2["type"] is FlowResultType.FORM
     assert result2["step_id"] == "pick_device"
     assert not result2["errors"]
 
@@ -118,29 +367,27 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
-    assert result["type"] == "form"
+    assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "user"
     assert not result["errors"]
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
         await hass.async_block_till_done()
 
-    assert result2["type"] == "form"
+    assert result2["type"] is FlowResultType.FORM
     assert result2["step_id"] == "pick_device"
     assert not result2["errors"]
 
-    with _patch_discovery(), _patch_single_discovery(), patch(
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch(
         f"{MODULE}.async_setup_entry", return_value=True
     ) as mock_setup_entry:
         result3 = await hass.config_entries.flow.async_configure(
             result["flow_id"], {CONF_DEVICE: MAC_ADDRESS}
         )
-        assert result3["type"] == "create_entry"
+        assert result3["type"] is FlowResultType.CREATE_ENTRY
         assert result3["title"] == DEFAULT_ENTRY_TITLE
-        assert result3["data"] == {
-            CONF_HOST: IP_ADDRESS,
-        }
+        assert result3["data"] == CREATE_ENTRY_DATA_LEGACY
         await hass.async_block_till_done()
 
     mock_setup_entry.assert_called_once()
@@ -149,15 +396,15 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
-    assert result["type"] == "form"
+    assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "user"
     assert not result["errors"]
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
         await hass.async_block_till_done()
 
-    assert result2["type"] == "abort"
+    assert result2["type"] is FlowResultType.ABORT
     assert result2["reason"] == "no_devices_found"
 
 
@@ -167,11 +414,11 @@ async def test_discovery_no_device(hass: HomeAssistant) -> None:
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
 
-    with _patch_discovery(no_device=True), _patch_single_discovery():
+    with _patch_discovery(no_device=True), _patch_single_discovery(), _patch_connect():
         result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
         await hass.async_block_till_done()
 
-    assert result2["type"] == "abort"
+    assert result2["type"] is FlowResultType.ABORT
     assert result2["reason"] == "no_devices_found"
 
 
@@ -180,46 +427,48 @@ async def test_manual(hass: HomeAssistant) -> None:
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
-    assert result["type"] == "form"
+    assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "user"
     assert not result["errors"]
 
     # Cannot connect (timeout)
-    with _patch_discovery(no_device=True), _patch_single_discovery(no_device=True):
+    with _patch_discovery(no_device=True), _patch_single_discovery(
+        no_device=True
+    ), _patch_connect(no_device=True):
         result2 = await hass.config_entries.flow.async_configure(
             result["flow_id"], {CONF_HOST: IP_ADDRESS}
         )
         await hass.async_block_till_done()
 
-    assert result2["type"] == "form"
+    assert result2["type"] is FlowResultType.FORM
     assert result2["step_id"] == "user"
     assert result2["errors"] == {"base": "cannot_connect"}
 
     # Success
-    with _patch_discovery(), _patch_single_discovery(), patch(
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch(
         f"{MODULE}.async_setup", return_value=True
     ), patch(f"{MODULE}.async_setup_entry", return_value=True):
         result4 = await hass.config_entries.flow.async_configure(
             result["flow_id"], {CONF_HOST: IP_ADDRESS}
         )
         await hass.async_block_till_done()
-    assert result4["type"] == "create_entry"
+    assert result4["type"] is FlowResultType.CREATE_ENTRY
     assert result4["title"] == DEFAULT_ENTRY_TITLE
-    assert result4["data"] == {
-        CONF_HOST: IP_ADDRESS,
-    }
+    assert result4["data"] == CREATE_ENTRY_DATA_LEGACY
 
     # Duplicate
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
-    with _patch_discovery(no_device=True), _patch_single_discovery(no_device=True):
+    with _patch_discovery(no_device=True), _patch_single_discovery(
+        no_device=True
+    ), _patch_connect(no_device=True):
         result2 = await hass.config_entries.flow.async_configure(
             result["flow_id"], {CONF_HOST: IP_ADDRESS}
         )
         await hass.async_block_till_done()
 
-    assert result2["type"] == "abort"
+    assert result2["type"] is FlowResultType.ABORT
     assert result2["reason"] == "already_configured"
 
 
@@ -228,11 +477,13 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None:
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
-    assert result["type"] == "form"
+    assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "user"
     assert not result["errors"]
 
-    with _patch_discovery(no_device=True), _patch_single_discovery(), patch(
+    with _patch_discovery(
+        no_device=True
+    ), _patch_single_discovery(), _patch_connect(), patch(
         f"{MODULE}.async_setup", return_value=True
     ), patch(f"{MODULE}.async_setup_entry", return_value=True):
         result = await hass.config_entries.flow.async_configure(
@@ -240,26 +491,133 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None:
         )
         await hass.async_block_till_done()
 
-    assert result["type"] == "create_entry"
-    assert result["data"] == {
-        CONF_HOST: IP_ADDRESS,
-    }
+    assert result["type"] is FlowResultType.CREATE_ENTRY
+    assert result["data"] == CREATE_ENTRY_DATA_LEGACY
+
+
+async def test_manual_auth(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    mock_init,
+) -> None:
+    """Test manually setup."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "user"
+    assert not result["errors"]
+
+    mock_discovery["mock_device"].update.side_effect = AuthenticationException
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"], user_input={CONF_HOST: IP_ADDRESS}
+    )
+    await hass.async_block_till_done()
+
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["step_id"] == "user_auth_confirm"
+    assert not result2["errors"]
+
+    mock_discovery["mock_device"].update.reset_mock(side_effect=True)
+
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    await hass.async_block_till_done()
+    assert result3["type"] is FlowResultType.CREATE_ENTRY
+    assert result3["title"] == DEFAULT_ENTRY_TITLE
+    assert result3["data"] == CREATE_ENTRY_DATA_AUTH
+
+
+@pytest.mark.parametrize(
+    ("error_type", "errors_msg", "error_placement"),
+    [
+        (AuthenticationException, "invalid_auth", CONF_PASSWORD),
+        (SmartDeviceException, "cannot_connect", "base"),
+    ],
+    ids=["invalid-auth", "unknown-error"],
+)
+async def test_manual_auth_errors(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    mock_init,
+    error_type,
+    errors_msg,
+    error_placement,
+) -> None:
+    """Test manually setup auth errors."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "user"
+    assert not result["errors"]
+
+    mock_discovery["mock_device"].update.side_effect = AuthenticationException
+    default_connect_side_effect = mock_connect["connect"].side_effect
+    mock_connect["connect"].side_effect = error_type
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"], user_input={CONF_HOST: IP_ADDRESS}
+    )
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["step_id"] == "user_auth_confirm"
+    assert not result2["errors"]
+
+    await hass.async_block_till_done()
+
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    await hass.async_block_till_done()
+    assert result3["type"] is FlowResultType.FORM
+    assert result3["step_id"] == "user_auth_confirm"
+    assert result3["errors"] == {error_placement: errors_msg}
+
+    mock_connect["connect"].side_effect = default_connect_side_effect
+    result4 = await hass.config_entries.flow.async_configure(
+        result3["flow_id"],
+        {
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    assert result4["type"] is FlowResultType.CREATE_ENTRY
+    assert result4["data"] == CREATE_ENTRY_DATA_AUTH
+
+    await hass.async_block_till_done()
 
 
 async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
     """Test we get the form with discovery and abort for dhcp source when we get both."""
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result = await hass.config_entries.flow.async_init(
             DOMAIN,
             context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
-            data={CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_NAME: ALIAS},
+            data={
+                CONF_HOST: IP_ADDRESS,
+                CONF_MAC: MAC_ADDRESS,
+                CONF_ALIAS: ALIAS,
+                CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
+            },
         )
         await hass.async_block_till_done()
-    assert result["type"] == FlowResultType.FORM
+    assert result["type"] is FlowResultType.FORM
     assert result["errors"] is None
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result2 = await hass.config_entries.flow.async_init(
             DOMAIN,
             context={"source": config_entries.SOURCE_DHCP},
@@ -268,10 +626,10 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
             ),
         )
         await hass.async_block_till_done()
-    assert result2["type"] == FlowResultType.ABORT
+    assert result2["type"] is FlowResultType.ABORT
     assert result2["reason"] == "already_in_progress"
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result3 = await hass.config_entries.flow.async_init(
             DOMAIN,
             context={"source": config_entries.SOURCE_DHCP},
@@ -280,10 +638,12 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
             ),
         )
         await hass.async_block_till_done()
-    assert result3["type"] == FlowResultType.ABORT
+    assert result3["type"] is FlowResultType.ABORT
     assert result3["reason"] == "already_in_progress"
 
-    with _patch_discovery(no_device=True), _patch_single_discovery(no_device=True):
+    with _patch_discovery(no_device=True), _patch_single_discovery(
+        no_device=True
+    ), _patch_connect(no_device=True):
         result3 = await hass.config_entries.flow.async_init(
             DOMAIN,
             context={"source": config_entries.SOURCE_DHCP},
@@ -305,7 +665,12 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
         ),
         (
             config_entries.SOURCE_INTEGRATION_DISCOVERY,
-            {CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_NAME: ALIAS},
+            {
+                CONF_HOST: IP_ADDRESS,
+                CONF_MAC: MAC_ADDRESS,
+                CONF_ALIAS: ALIAS,
+                CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
+            },
         ),
     ],
 )
@@ -314,16 +679,16 @@ async def test_discovered_by_dhcp_or_discovery(
 ) -> None:
     """Test we can setup when discovered from dhcp or discovery."""
 
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         result = await hass.config_entries.flow.async_init(
             DOMAIN, context={"source": source}, data=data
         )
         await hass.async_block_till_done()
 
-    assert result["type"] == FlowResultType.FORM
+    assert result["type"] is FlowResultType.FORM
     assert result["errors"] is None
 
-    with _patch_discovery(), _patch_single_discovery(), patch(
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch(
         f"{MODULE}.async_setup", return_value=True
     ) as mock_async_setup, patch(
         f"{MODULE}.async_setup_entry", return_value=True
@@ -331,10 +696,8 @@ async def test_discovered_by_dhcp_or_discovery(
         result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
         await hass.async_block_till_done()
 
-    assert result2["type"] == "create_entry"
-    assert result2["data"] == {
-        CONF_HOST: IP_ADDRESS,
-    }
+    assert result2["type"] is FlowResultType.CREATE_ENTRY
+    assert result2["data"] == CREATE_ENTRY_DATA_LEGACY
     assert mock_async_setup.called
     assert mock_async_setup_entry.called
 
@@ -348,7 +711,12 @@ async def test_discovered_by_dhcp_or_discovery(
         ),
         (
             config_entries.SOURCE_INTEGRATION_DISCOVERY,
-            {CONF_HOST: IP_ADDRESS, CONF_MAC: MAC_ADDRESS, CONF_NAME: ALIAS},
+            {
+                CONF_HOST: IP_ADDRESS,
+                CONF_MAC: MAC_ADDRESS,
+                CONF_ALIAS: ALIAS,
+                CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
+            },
         ),
     ],
 )
@@ -357,10 +725,350 @@ async def test_discovered_by_dhcp_or_discovery_failed_to_get_device(
 ) -> None:
     """Test we abort if we cannot get the unique id when discovered from dhcp."""
 
-    with _patch_discovery(no_device=True), _patch_single_discovery(no_device=True):
+    with _patch_discovery(no_device=True), _patch_single_discovery(
+        no_device=True
+    ), _patch_connect(no_device=True):
         result = await hass.config_entries.flow.async_init(
             DOMAIN, context={"source": source}, data=data
         )
         await hass.async_block_till_done()
-    assert result["type"] == FlowResultType.ABORT
+    assert result["type"] is FlowResultType.ABORT
     assert result["reason"] == "cannot_connect"
+
+
+async def test_reauth(
+    hass: HomeAssistant,
+    mock_added_config_entry: MockConfigEntry,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+) -> None:
+    """Test reauth flow."""
+    mock_added_config_entry.async_start_reauth(hass)
+    await hass.async_block_till_done()
+
+    assert mock_added_config_entry.state == config_entries.ConfigEntryState.LOADED
+    flows = hass.config_entries.flow.async_progress()
+    assert len(flows) == 1
+    [result] = flows
+    assert result["step_id"] == "reauth_confirm"
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    credentials = Credentials("fake_username", "fake_password")
+    mock_discovery["discover_single"].assert_called_once_with(
+        "127.0.0.1", credentials=credentials
+    )
+    mock_discovery["mock_device"].update.assert_called_once_with()
+    assert result2["type"] is FlowResultType.ABORT
+    assert result2["reason"] == "reauth_successful"
+
+    await hass.async_block_till_done()
+
+
+async def test_reauth_update_from_discovery(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+) -> None:
+    """Test reauth flow."""
+    mock_connect["connect"].side_effect = AuthenticationException
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert mock_config_entry.state == config_entries.ConfigEntryState.SETUP_ERROR
+
+    flows = hass.config_entries.flow.async_progress()
+    assert len(flows) == 1
+    [result] = flows
+    assert result["step_id"] == "reauth_confirm"
+    assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
+
+    discovery_result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
+        data={
+            CONF_HOST: IP_ADDRESS,
+            CONF_MAC: MAC_ADDRESS,
+            CONF_ALIAS: ALIAS,
+            CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+        },
+    )
+    await hass.async_block_till_done()
+    assert discovery_result["type"] is FlowResultType.ABORT
+    assert discovery_result["reason"] == "already_configured"
+    assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH
+
+
+async def test_reauth_update_from_discovery_with_ip_change(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+) -> None:
+    """Test reauth flow."""
+    mock_connect["connect"].side_effect = AuthenticationException()
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+    assert mock_config_entry.state == config_entries.ConfigEntryState.SETUP_ERROR
+
+    flows = hass.config_entries.flow.async_progress()
+    assert len(flows) == 1
+    [result] = flows
+    assert result["step_id"] == "reauth_confirm"
+    assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
+
+    discovery_result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
+        data={
+            CONF_HOST: "127.0.0.2",
+            CONF_MAC: MAC_ADDRESS,
+            CONF_ALIAS: ALIAS,
+            CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+        },
+    )
+    await hass.async_block_till_done()
+    assert discovery_result["type"] is FlowResultType.ABORT
+    assert discovery_result["reason"] == "already_configured"
+    assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH
+    assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
+
+
+async def test_reauth_no_update_if_config_and_ip_the_same(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+) -> None:
+    """Test reauth discovery does not update when the host and config are the same."""
+    mock_connect["connect"].side_effect = AuthenticationException()
+    mock_config_entry.data = {
+        **mock_config_entry.data,
+        CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+    }
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+    assert mock_config_entry.state is config_entries.ConfigEntryState.SETUP_ERROR
+
+    flows = hass.config_entries.flow.async_progress()
+    assert len(flows) == 1
+    [result] = flows
+    assert result["step_id"] == "reauth_confirm"
+    assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH
+
+    discovery_result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
+        data={
+            CONF_HOST: IP_ADDRESS,
+            CONF_MAC: MAC_ADDRESS,
+            CONF_ALIAS: ALIAS,
+            CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AUTH,
+        },
+    )
+    await hass.async_block_till_done()
+    assert discovery_result["type"] is FlowResultType.ABORT
+    assert discovery_result["reason"] == "already_configured"
+    assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AUTH
+    assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
+
+
+@pytest.mark.parametrize(
+    ("error_type", "errors_msg", "error_placement"),
+    [
+        (AuthenticationException, "invalid_auth", CONF_PASSWORD),
+        (SmartDeviceException, "cannot_connect", "base"),
+    ],
+    ids=["invalid-auth", "unknown-error"],
+)
+async def test_reauth_errors(
+    hass: HomeAssistant,
+    mock_added_config_entry: MockConfigEntry,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    error_type,
+    errors_msg,
+    error_placement,
+) -> None:
+    """Test reauth errors."""
+    mock_added_config_entry.async_start_reauth(hass)
+    await hass.async_block_till_done()
+
+    assert mock_added_config_entry.state is config_entries.ConfigEntryState.LOADED
+    flows = hass.config_entries.flow.async_progress()
+    assert len(flows) == 1
+    [result] = flows
+    assert result["step_id"] == "reauth_confirm"
+
+    mock_discovery["mock_device"].update.side_effect = error_type
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    credentials = Credentials("fake_username", "fake_password")
+
+    mock_discovery["discover_single"].assert_called_once_with(
+        "127.0.0.1", credentials=credentials
+    )
+    mock_discovery["mock_device"].update.assert_called_once_with()
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["errors"] == {error_placement: errors_msg}
+
+    mock_discovery["discover_single"].reset_mock()
+    mock_discovery["mock_device"].update.reset_mock(side_effect=True)
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+
+    mock_discovery["discover_single"].assert_called_once_with(
+        "127.0.0.1", credentials=credentials
+    )
+    mock_discovery["mock_device"].update.assert_called_once_with()
+
+    assert result3["type"] is FlowResultType.ABORT
+    assert result3["reason"] == "reauth_successful"
+
+
+@pytest.mark.parametrize(
+    ("error_type", "expected_flow"),
+    [
+        (AuthenticationException, FlowResultType.FORM),
+        (SmartDeviceException, FlowResultType.ABORT),
+    ],
+    ids=["invalid-auth", "unknown-error"],
+)
+async def test_pick_device_errors(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    error_type,
+    expected_flow,
+) -> None:
+    """Test errors on pick_device."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    await hass.async_block_till_done()
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "user"
+    assert not result["errors"]
+
+    result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
+    await hass.async_block_till_done()
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["step_id"] == "pick_device"
+    assert not result2["errors"]
+
+    default_connect_side_effect = mock_connect["connect"].side_effect
+    mock_connect["connect"].side_effect = error_type
+    result3 = await hass.config_entries.flow.async_configure(
+        result2["flow_id"],
+        {CONF_DEVICE: MAC_ADDRESS},
+    )
+    await hass.async_block_till_done()
+    assert result3["type"] == expected_flow
+
+    if expected_flow != FlowResultType.ABORT:
+        mock_connect["connect"].side_effect = default_connect_side_effect
+        result4 = await hass.config_entries.flow.async_configure(
+            result3["flow_id"],
+            user_input={
+                CONF_USERNAME: "fake_username",
+                CONF_PASSWORD: "fake_password",
+            },
+        )
+        assert result4["type"] == FlowResultType.CREATE_ENTRY
+
+
+async def test_discovery_timeout_connect(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    mock_init,
+) -> None:
+    """Test discovery tries legacy connect on timeout."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    mock_discovery["discover_single"].side_effect = TimeoutException
+    await hass.async_block_till_done()
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "user"
+    assert not result["errors"]
+    assert mock_connect["connect"].call_count == 0
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"], {CONF_HOST: IP_ADDRESS}
+    )
+    await hass.async_block_till_done()
+    assert result2["type"] is FlowResultType.CREATE_ENTRY
+    assert mock_connect["connect"].call_count == 1
+
+
+async def test_reauth_update_other_flows(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    # mock_init,
+) -> None:
+    """Test reauth updates other reauth flows."""
+    mock_config_entry2 = MockConfigEntry(
+        title="TPLink",
+        domain=DOMAIN,
+        data={**CREATE_ENTRY_DATA_AUTH2},
+        unique_id=MAC_ADDRESS2,
+    )
+    default_side_effect = mock_connect["connect"].side_effect
+    mock_connect["connect"].side_effect = AuthenticationException()
+    mock_config_entry.add_to_hass(hass)
+    mock_config_entry2.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    assert mock_config_entry2.state == config_entries.ConfigEntryState.SETUP_ERROR
+    assert mock_config_entry.state == config_entries.ConfigEntryState.SETUP_ERROR
+    mock_connect["connect"].side_effect = default_side_effect
+
+    await hass.async_block_till_done()
+
+    flows = hass.config_entries.flow.async_progress()
+    assert len(flows) == 2
+    result = flows[0]
+    assert result["step_id"] == "reauth_confirm"
+    assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={
+            CONF_USERNAME: "fake_username",
+            CONF_PASSWORD: "fake_password",
+        },
+    )
+    credentials = Credentials("fake_username", "fake_password")
+    mock_discovery["discover_single"].assert_called_once_with(
+        "127.0.0.1", credentials=credentials
+    )
+    mock_discovery["mock_device"].update.assert_called_once_with()
+    assert result2["type"] is FlowResultType.ABORT
+    assert result2["reason"] == "reauth_successful"
+
+    await hass.async_block_till_done()
+    flows = hass.config_entries.flow.async_progress()
+    assert len(flows) == 0
diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py
index c40560d2a89c5f13abac3af62004e5f56fd03c53..e6297cf65539bcda101b8a665353026937297b39 100644
--- a/tests/components/tplink/test_init.py
+++ b/tests/components/tplink/test_init.py
@@ -1,25 +1,35 @@
 """Tests for the TP-Link component."""
 from __future__ import annotations
 
+import copy
 from datetime import timedelta
-from unittest.mock import MagicMock, patch
+from unittest.mock import AsyncMock, MagicMock, patch
 
 import pytest
 
 from homeassistant import setup
 from homeassistant.components import tplink
-from homeassistant.components.tplink.const import DOMAIN
-from homeassistant.config_entries import ConfigEntryState
-from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED
+from homeassistant.components.tplink.const import CONF_DEVICE_CONFIG, DOMAIN
+from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
+from homeassistant.const import (
+    CONF_AUTHENTICATION,
+    CONF_HOST,
+    CONF_PASSWORD,
+    CONF_USERNAME,
+    EVENT_HOMEASSISTANT_STARTED,
+)
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers.entity_registry import EntityRegistry
 from homeassistant.setup import async_setup_component
 from homeassistant.util import dt as dt_util
 
 from . import (
+    CREATE_ENTRY_DATA_AUTH,
+    DEVICE_CONFIG_AUTH,
     IP_ADDRESS,
     MAC_ADDRESS,
     _mocked_dimmer,
+    _patch_connect,
     _patch_discovery,
     _patch_single_discovery,
 )
@@ -57,7 +67,7 @@ async def test_config_entry_reload(hass: HomeAssistant) -> None:
         domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
     )
     already_migrated_config_entry.add_to_hass(hass)
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
         assert already_migrated_config_entry.state == ConfigEntryState.LOADED
@@ -72,7 +82,9 @@ async def test_config_entry_retry(hass: HomeAssistant) -> None:
         domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=MAC_ADDRESS
     )
     already_migrated_config_entry.add_to_hass(hass)
-    with _patch_discovery(no_device=True), _patch_single_discovery(no_device=True):
+    with _patch_discovery(no_device=True), _patch_single_discovery(
+        no_device=True
+    ), _patch_connect(no_device=True):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
         assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY
@@ -102,7 +114,9 @@ async def test_dimmer_switch_unique_id_fix_original_entity_still_exists(
         original_name="Rollout dimmer",
     )
 
-    with _patch_discovery(device=dimmer), _patch_single_discovery(device=dimmer):
+    with _patch_discovery(device=dimmer), _patch_single_discovery(
+        device=dimmer
+    ), _patch_connect(device=dimmer):
         await setup.async_setup_component(hass, DOMAIN, {})
         await hass.async_block_till_done()
 
@@ -126,7 +140,7 @@ async def test_config_entry_wrong_mac_Address(
         domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=mismatched_mac
     )
     already_migrated_config_entry.add_to_hass(hass)
-    with _patch_discovery(), _patch_single_discovery():
+    with _patch_discovery(), _patch_single_discovery(), _patch_connect():
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
         assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY
@@ -135,3 +149,110 @@ async def test_config_entry_wrong_mac_Address(
         "Unexpected device found at 127.0.0.1; expected aa:bb:cc:dd:ee:f0, found aa:bb:cc:dd:ee:ff"
         in caplog.text
     )
+
+
+async def test_config_entry_device_config(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+) -> None:
+    """Test that a config entry can be loaded with DeviceConfig."""
+    mock_config_entry = MockConfigEntry(
+        title="TPLink",
+        domain=DOMAIN,
+        data={**CREATE_ENTRY_DATA_AUTH},
+        unique_id=MAC_ADDRESS,
+    )
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+    assert mock_config_entry.state is ConfigEntryState.LOADED
+
+
+async def test_config_entry_with_stored_credentials(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+) -> None:
+    """Test that a config entry can be loaded when stored credentials are set."""
+    stored_credentials = tplink.Credentials("fake_username1", "fake_password1")
+    mock_config_entry = MockConfigEntry(
+        title="TPLink",
+        domain=DOMAIN,
+        data={**CREATE_ENTRY_DATA_AUTH},
+        unique_id=MAC_ADDRESS,
+    )
+    auth = {
+        CONF_USERNAME: stored_credentials.username,
+        CONF_PASSWORD: stored_credentials.password,
+    }
+
+    hass.data.setdefault(DOMAIN, {})[CONF_AUTHENTICATION] = auth
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+    assert mock_config_entry.state is ConfigEntryState.LOADED
+    config = DEVICE_CONFIG_AUTH
+    assert config.credentials != stored_credentials
+    config.credentials = stored_credentials
+    mock_connect["connect"].assert_called_once_with(config=config)
+
+
+async def test_config_entry_device_config_invalid(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    caplog,
+) -> None:
+    """Test that an invalid device config logs an error and loads the config entry."""
+    entry_data = copy.deepcopy(CREATE_ENTRY_DATA_AUTH)
+    entry_data[CONF_DEVICE_CONFIG] = {"foo": "bar"}
+    mock_config_entry = MockConfigEntry(
+        title="TPLink",
+        domain=DOMAIN,
+        data={**entry_data},
+        unique_id=MAC_ADDRESS,
+    )
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+    assert mock_config_entry.state is ConfigEntryState.LOADED
+
+    assert (
+        f"Invalid connection type dict for {IP_ADDRESS}: {entry_data.get(CONF_DEVICE_CONFIG)}"
+        in caplog.text
+    )
+
+
+@pytest.mark.parametrize(
+    ("error_type", "entry_state", "reauth_flows"),
+    [
+        (tplink.AuthenticationException, ConfigEntryState.SETUP_ERROR, True),
+        (tplink.SmartDeviceException, ConfigEntryState.SETUP_RETRY, False),
+    ],
+    ids=["invalid-auth", "unknown-error"],
+)
+async def test_config_entry_errors(
+    hass: HomeAssistant,
+    mock_discovery: AsyncMock,
+    mock_connect: AsyncMock,
+    error_type,
+    entry_state,
+    reauth_flows,
+) -> None:
+    """Test that device exceptions are handled correctly during init."""
+    mock_connect["connect"].side_effect = error_type
+    mock_config_entry = MockConfigEntry(
+        title="TPLink",
+        domain=DOMAIN,
+        data={**CREATE_ENTRY_DATA_AUTH},
+        unique_id=MAC_ADDRESS,
+    )
+    mock_config_entry.add_to_hass(hass)
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+    assert mock_config_entry.state is entry_state
+    assert (
+        any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
+        == reauth_flows
+    )
diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py
index ada454e0192bf25985e688b3f268fb8ee04c7416..c541551a2503c4926cc566d4059fa3961ce44f37 100644
--- a/tests/components/tplink/test_light.py
+++ b/tests/components/tplink/test_light.py
@@ -33,6 +33,7 @@ from . import (
     MAC_ADDRESS,
     _mocked_bulb,
     _mocked_smart_light_strip,
+    _patch_connect,
     _patch_discovery,
     _patch_single_discovery,
 )
@@ -48,7 +49,7 @@ async def test_light_unique_id(hass: HomeAssistant) -> None:
     already_migrated_config_entry.add_to_hass(hass)
     bulb = _mocked_bulb()
     bulb.color_temp = None
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -69,7 +70,7 @@ async def test_color_light(
     )
     already_migrated_config_entry.add_to_hass(hass)
     bulb.color_temp = None
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -151,7 +152,7 @@ async def test_color_light_no_temp(hass: HomeAssistant) -> None:
     bulb = _mocked_bulb()
     bulb.is_variable_color_temp = False
     type(bulb).color_temp = PropertyMock(side_effect=Exception)
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -212,7 +213,7 @@ async def test_color_temp_light(
     bulb.color_temp = 4000
     bulb.is_variable_color_temp = True
 
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -295,7 +296,7 @@ async def test_brightness_only_light(hass: HomeAssistant) -> None:
     bulb.is_color = False
     bulb.is_variable_color_temp = False
 
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -340,7 +341,7 @@ async def test_on_off_light(hass: HomeAssistant) -> None:
     bulb.is_variable_color_temp = False
     bulb.is_dimmable = False
 
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -375,7 +376,7 @@ async def test_off_at_start_light(hass: HomeAssistant) -> None:
     bulb.is_dimmable = False
     bulb.is_on = False
 
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -397,7 +398,7 @@ async def test_dimmer_turn_on_fix(hass: HomeAssistant) -> None:
     bulb.is_dimmer = True
     bulb.is_on = False
 
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -421,7 +422,9 @@ async def test_smart_strip_effects(hass: HomeAssistant) -> None:
     already_migrated_config_entry.add_to_hass(hass)
     strip = _mocked_smart_light_strip()
 
-    with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
+    with _patch_discovery(device=strip), _patch_single_discovery(
+        device=strip
+    ), _patch_connect(device=strip):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -501,7 +504,7 @@ async def test_smart_strip_custom_random_effect(hass: HomeAssistant) -> None:
     already_migrated_config_entry.add_to_hass(hass)
     strip = _mocked_smart_light_strip()
 
-    with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
+    with _patch_discovery(device=strip), _patch_connect(device=strip):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -664,7 +667,7 @@ async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) ->
         "name": "Custom",
         "enable": 0,
     }
-    with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
+    with _patch_discovery(device=strip), _patch_connect(device=strip):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -691,7 +694,7 @@ async def test_smart_strip_custom_sequence_effect(hass: HomeAssistant) -> None:
     already_migrated_config_entry.add_to_hass(hass)
     strip = _mocked_smart_light_strip()
 
-    with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
+    with _patch_discovery(device=strip), _patch_connect(device=strip):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
diff --git a/tests/components/tplink/test_sensor.py b/tests/components/tplink/test_sensor.py
index 5413e036d96de1f8cbc887996374f4d75d09b4f9..b67ed031df38678e3caf2bc6feef788f388c697f 100644
--- a/tests/components/tplink/test_sensor.py
+++ b/tests/components/tplink/test_sensor.py
@@ -8,13 +8,7 @@ from homeassistant.core import HomeAssistant
 from homeassistant.helpers import entity_registry as er
 from homeassistant.setup import async_setup_component
 
-from . import (
-    MAC_ADDRESS,
-    _mocked_bulb,
-    _mocked_plug,
-    _patch_discovery,
-    _patch_single_discovery,
-)
+from . import MAC_ADDRESS, _mocked_bulb, _mocked_plug, _patch_connect, _patch_discovery
 
 from tests.common import MockConfigEntry
 
@@ -35,7 +29,7 @@ async def test_color_light_with_an_emeter(hass: HomeAssistant) -> None:
         current=5,
     )
     bulb.emeter_today = 5000.0036
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
         await hass.async_block_till_done()
@@ -75,7 +69,7 @@ async def test_plug_with_an_emeter(hass: HomeAssistant) -> None:
         current=5.035,
     )
     plug.emeter_today = None
-    with _patch_discovery(device=plug), _patch_single_discovery(device=plug):
+    with _patch_discovery(device=plug), _patch_connect(device=plug):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
         await hass.async_block_till_done()
@@ -103,7 +97,7 @@ async def test_color_light_no_emeter(hass: HomeAssistant) -> None:
     bulb = _mocked_bulb()
     bulb.color_temp = None
     bulb.has_emeter = False
-    with _patch_discovery(device=bulb), _patch_single_discovery(device=bulb):
+    with _patch_discovery(device=bulb), _patch_connect(device=bulb):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
         await hass.async_block_till_done()
@@ -139,7 +133,7 @@ async def test_sensor_unique_id(hass: HomeAssistant) -> None:
         current=5,
     )
     plug.emeter_today = None
-    with _patch_discovery(device=plug), _patch_single_discovery(device=plug):
+    with _patch_discovery(device=plug), _patch_connect(device=plug):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
diff --git a/tests/components/tplink/test_switch.py b/tests/components/tplink/test_switch.py
index 05286e5ff48cb9808f39b0f1c6745b254bb89bab..372651ea250c0221b93e8dd73da324f49c598d7f 100644
--- a/tests/components/tplink/test_switch.py
+++ b/tests/components/tplink/test_switch.py
@@ -20,8 +20,8 @@ from . import (
     _mocked_dimmer,
     _mocked_plug,
     _mocked_strip,
+    _patch_connect,
     _patch_discovery,
-    _patch_single_discovery,
 )
 
 from tests.common import MockConfigEntry, async_fire_time_changed
@@ -34,7 +34,7 @@ async def test_plug(hass: HomeAssistant) -> None:
     )
     already_migrated_config_entry.add_to_hass(hass)
     plug = _mocked_plug()
-    with _patch_discovery(device=plug), _patch_single_discovery(device=plug):
+    with _patch_discovery(device=plug), _patch_connect(device=plug):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -69,7 +69,7 @@ async def test_led_switch(hass: HomeAssistant, dev, domain: str) -> None:
         domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
     )
     already_migrated_config_entry.add_to_hass(hass)
-    with _patch_discovery(device=dev), _patch_single_discovery(device=dev):
+    with _patch_discovery(device=dev), _patch_connect(device=dev):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -100,7 +100,7 @@ async def test_plug_unique_id(hass: HomeAssistant) -> None:
     )
     already_migrated_config_entry.add_to_hass(hass)
     plug = _mocked_plug()
-    with _patch_discovery(device=plug), _patch_single_discovery(device=plug):
+    with _patch_discovery(device=plug), _patch_connect(device=plug):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -116,7 +116,7 @@ async def test_plug_update_fails(hass: HomeAssistant) -> None:
     )
     already_migrated_config_entry.add_to_hass(hass)
     plug = _mocked_plug()
-    with _patch_discovery(device=plug), _patch_single_discovery(device=plug):
+    with _patch_discovery(device=plug), _patch_connect(device=plug):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -138,7 +138,7 @@ async def test_strip(hass: HomeAssistant) -> None:
     )
     already_migrated_config_entry.add_to_hass(hass)
     strip = _mocked_strip()
-    with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
+    with _patch_discovery(device=strip), _patch_connect(device=strip):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()
 
@@ -186,7 +186,7 @@ async def test_strip_unique_ids(hass: HomeAssistant) -> None:
     )
     already_migrated_config_entry.add_to_hass(hass)
     strip = _mocked_strip()
-    with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
+    with _patch_discovery(device=strip), _patch_connect(device=strip):
         await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
         await hass.async_block_till_done()