diff --git a/homeassistant/const.py b/homeassistant/const.py
index e1e9757dd02bb6cb5c58bbf6c7fb4e087365c40b..449e7a90087b83702207c87a575f80e5a1f56c5e 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -40,6 +40,8 @@ CONF_BELOW = "below"
 CONF_BINARY_SENSORS = "binary_sensors"
 CONF_BLACKLIST = "blacklist"
 CONF_BRIGHTNESS = "brightness"
+CONF_CLIENT_ID = "client_id"
+CONF_CLIENT_SECRET = "client_secret"
 CONF_CODE = "code"
 CONF_COLOR_TEMP = "color_temp"
 CONF_COMMAND = "command"
diff --git a/script/scaffold/__main__.py b/script/scaffold/__main__.py
index 2258840f430d927f537c974eb9c161f456bf5c3c..78490b84ba3d9b1fbc429ca9afdf1cf4195cd13e 100644
--- a/script/scaffold/__main__.py
+++ b/script/scaffold/__main__.py
@@ -48,34 +48,46 @@ def main():
     args = get_arguments()
 
     info = gather_info.gather_info(args)
+    print()
+
+    # If we are calling scaffold on a non-existing integration,
+    # We're going to first make it. If we're making an integration,
+    # we will also make a config flow to go with it.
+
+    if info.is_new:
+        generate.generate("integration", info)
 
-    generate.generate(args.template, info)
+        # If it's a new integration and it's not a config flow,
+        # create a config flow too.
+        if not args.template.startswith("config_flow"):
+            if info.oauth2:
+                template = "config_flow_oauth2"
+            elif info.authentication or not info.discoverable:
+                template = "config_flow"
+            else:
+                template = "config_flow_discovery"
 
-    # If creating new integration, create config flow too
-    if args.template == "integration":
-        if info.authentication or not info.discoverable:
-            template = "config_flow"
-        else:
-            template = "config_flow_discovery"
+            generate.generate(template, info)
 
-        generate.generate(template, info)
+    # If we wanted a new integration, we've already done our work.
+    if args.template != "integration":
+        generate.generate(args.template, info)
+
+    pipe_null = "" if args.develop else "> /dev/null"
 
     print("Running hassfest to pick up new information.")
-    subprocess.run("python -m script.hassfest", shell=True)
+    subprocess.run(f"python -m script.hassfest {pipe_null}", shell=True)
     print()
 
-    print("Running tests")
-    print(f"$ pytest -vvv tests/components/{info.domain}")
-    if (
-        subprocess.run(
-            f"pytest -vvv tests/components/{info.domain}", shell=True
-        ).returncode
-        != 0
-    ):
-        return 1
+    print("Running gen_requirements_all to pick up new information.")
+    subprocess.run(f"python -m script.gen_requirements_all {pipe_null}", shell=True)
     print()
 
-    print(f"Done!")
+    if args.develop:
+        print("Running tests")
+        print(f"$ pytest -vvv tests/components/{info.domain}")
+        subprocess.run(f"pytest -vvv tests/components/{info.domain}", shell=True)
+        print()
 
     docs.print_relevant_docs(args.template, info)
 
diff --git a/script/scaffold/docs.py b/script/scaffold/docs.py
index bb119c0e42e4b3cf2dbca6f39d74b71ed4057e09..5df663fec0b2409a2f7724aa6a2bab9243773853 100644
--- a/script/scaffold/docs.py
+++ b/script/scaffold/docs.py
@@ -2,72 +2,76 @@
 from .model import Info
 
 
-def print_relevant_docs(template: str, info: Info) -> None:
-    """Print relevant docs."""
-    if template == "integration":
-        print(
-            f"""
-Your integration has been created at {info.integration_dir} . Next step is to fill in the blanks for the code marked with TODO.
-
-For a breakdown of each file, check the developer documentation at:
-https://developers.home-assistant.io/docs/en/creating_integration_file_structure.html
-"""
-        )
+DATA = {
+    "config_flow": {
+        "title": "Config Flow",
+        "docs": "https://developers.home-assistant.io/docs/en/config_entries_config_flow_handler.html",
+    },
+    "config_flow_discovery": {
+        "title": "Discoverable Config Flow",
+        "docs": "https://developers.home-assistant.io/docs/en/config_entries_config_flow_handler.html#discoverable-integrations-that-require-no-authentication",
+    },
+    "config_flow_oauth2": {
+        "title": "OAuth2 Config Flow",
+        "docs": "https://developers.home-assistant.io/docs/en/next/config_entries_config_flow_handler.html#configuration-via-oauth2",
+    },
+    "device_action": {
+        "title": "Device Action",
+        "docs": "https://developers.home-assistant.io/docs/en/device_automation_action.html",
+    },
+    "device_condition": {
+        "title": "Device Condition",
+        "docs": "https://developers.home-assistant.io/docs/en/device_automation_condition.html",
+    },
+    "device_trigger": {
+        "title": "Device Trigger",
+        "docs": "https://developers.home-assistant.io/docs/en/device_automation_trigger.html",
+    },
+    "integration": {
+        "title": "Integration",
+        "docs": "https://developers.home-assistant.io/docs/en/creating_integration_file_structure.html",
+    },
+    "reproduce_state": {
+        "title": "Reproduce State",
+        "docs": "https://developers.home-assistant.io/docs/en/reproduce_state_index.html",
+        "extra": "You will now need to update the code to make sure that every attribute that can occur in the state will cause the right service to be called.",
+    },
+}
 
-    elif template == "config_flow":
-        print(
-            f"""
-The config flow has been added to the {info.domain} integration. Next step is to fill in the blanks for the code marked with TODO.
-"""
-        )
-
-    elif template == "reproduce_state":
-        print(
-            f"""
-Reproduce state code has been added to the {info.domain} integration:
- - {info.integration_dir / "reproduce_state.py"}
- - {info.tests_dir / "test_reproduce_state.py"}
 
-You will now need to update the code to make sure that every attribute
-that can occur in the state will cause the right service to be called.
-"""
-        )
+def print_relevant_docs(template: str, info: Info) -> None:
+    """Print relevant docs."""
+    data = DATA[template]
 
-    elif template == "device_trigger":
-        print(
-            f"""
-Device trigger base has been added to the {info.domain} integration:
- - {info.integration_dir / "device_trigger.py"}
- - {info.integration_dir / "strings.json"} (translations)
- - {info.tests_dir / "test_device_trigger.py"}
+    print()
+    print("**************************")
+    print()
+    print()
+    print(f"{data['title']} code has been generated")
+    print()
+    if info.files_added:
+        print("Added the following files:")
+        for file in info.files_added:
+            print(f"- {file}")
+        print()
 
-You will now need to update the code to make sure that relevant triggers
-are exposed.
-"""
-        )
+    if info.tests_added:
+        print("Added the following tests:")
+        for file in info.tests_added:
+            print(f"- {file}")
+        print()
 
-    elif template == "device_condition":
+    if info.examples_added:
         print(
-            f"""
-Device condition base has been added to the {info.domain} integration:
- - {info.integration_dir / "device_condition.py"}
- - {info.integration_dir / "strings.json"} (translations)
- - {info.tests_dir / "test_device_condition.py"}
-
-You will now need to update the code to make sure that relevant condtions
-are exposed.
-"""
+            "Because some files already existed, we added the following example files. Please copy the relevant code to the existing files."
         )
+        for file in info.examples_added:
+            print(f"- {file}")
+        print()
 
-    elif template == "device_action":
-        print(
-            f"""
-Device action base has been added to the {info.domain} integration:
- - {info.integration_dir / "device_action.py"}
- - {info.integration_dir / "strings.json"} (translations)
- - {info.tests_dir / "test_device_action.py"}
+    print(
+        f"The next step is to look at the files and deal with all areas marked as TODO."
+    )
 
-You will now need to update the code to make sure that relevant services
-are exposed as actions.
-"""
-        )
+    if "extra" in data:
+        print(data["extra"])
diff --git a/script/scaffold/gather_info.py b/script/scaffold/gather_info.py
index a7263daaf41982026a2f1e8df20f3fdb97d9d6ac..12cb319d188ce0d41c0d43f63390179d91e8f402 100644
--- a/script/scaffold/gather_info.py
+++ b/script/scaffold/gather_info.py
@@ -13,36 +13,14 @@ CHECK_EMPTY = ["Cannot be empty", lambda value: value]
 
 def gather_info(arguments) -> Info:
     """Gather info."""
-    existing = arguments.template != "integration"
-
-    if arguments.develop:
+    if arguments.integration:
+        info = {"domain": arguments.integration}
+    elif arguments.develop:
         print("Running in developer mode. Automatically filling in info.")
         print()
-
-    if existing:
-        if arguments.develop:
-            return _load_existing_integration("develop")
-
-        if arguments.integration:
-            return _load_existing_integration(arguments.integration)
-
-        return gather_existing_integration()
-
-    if arguments.develop:
-        return Info(
-            domain="develop",
-            name="Develop Hub",
-            codeowner="@developer",
-            requirement="aiodevelop==1.2.3",
-        )
-
-    return gather_new_integration()
-
-
-def gather_new_integration() -> Info:
-    """Gather info about new integration from user."""
-    return Info(
-        **_gather_info(
+        info = {"domain": "develop"}
+    else:
+        info = _gather_info(
             {
                 "domain": {
                     "prompt": "What is the domain?",
@@ -52,84 +30,87 @@ def gather_new_integration() -> Info:
                             "Domains cannot contain spaces or special characters.",
                             lambda value: value == slugify(value),
                         ],
-                        [
-                            "There already is an integration with this domain.",
-                            lambda value: not (COMPONENT_DIR / value).exists(),
-                        ],
                     ],
-                },
-                "name": {
-                    "prompt": "What is the name of your integration?",
-                    "validators": [CHECK_EMPTY],
-                },
-                "codeowner": {
-                    "prompt": "What is your GitHub handle?",
-                    "validators": [
-                        CHECK_EMPTY,
-                        [
-                            'GitHub handles need to start with an "@"',
-                            lambda value: value.startswith("@"),
-                        ],
-                    ],
-                },
-                "requirement": {
-                    "prompt": "What PyPI package and version do you depend on? Leave blank for none.",
-                    "validators": [
-                        [
-                            "Versions should be pinned using '=='.",
-                            lambda value: not value or "==" in value,
-                        ]
-                    ],
-                },
+                }
+            }
+        )
+
+    info["is_new"] = not (COMPONENT_DIR / info["domain"] / "manifest.json").exists()
+
+    if not info["is_new"]:
+        return _load_existing_integration(info["domain"])
+
+    if arguments.develop:
+        info.update(
+            {
+                "name": "Develop Hub",
+                "codeowner": "@developer",
+                "requirement": "aiodevelop==1.2.3",
+                "oauth2": True,
+            }
+        )
+    else:
+        info.update(gather_new_integration(arguments.template == "integration"))
+
+    return Info(**info)
+
+
+YES_NO = {
+    "validators": [["Type either 'yes' or 'no'", lambda value: value in ("yes", "no")]],
+    "convertor": lambda value: value == "yes",
+}
+
+
+def gather_new_integration(determine_auth: bool) -> Info:
+    """Gather info about new integration from user."""
+    fields = {
+        "name": {
+            "prompt": "What is the name of your integration?",
+            "validators": [CHECK_EMPTY],
+        },
+        "codeowner": {
+            "prompt": "What is your GitHub handle?",
+            "validators": [
+                CHECK_EMPTY,
+                [
+                    'GitHub handles need to start with an "@"',
+                    lambda value: value.startswith("@"),
+                ],
+            ],
+        },
+        "requirement": {
+            "prompt": "What PyPI package and version do you depend on? Leave blank for none.",
+            "validators": [
+                [
+                    "Versions should be pinned using '=='.",
+                    lambda value: not value or "==" in value,
+                ]
+            ],
+        },
+    }
+
+    if determine_auth:
+        fields.update(
+            {
                 "authentication": {
                     "prompt": "Does Home Assistant need the user to authenticate to control the device/service? (yes/no)",
                     "default": "yes",
-                    "validators": [
-                        [
-                            "Type either 'yes' or 'no'",
-                            lambda value: value in ("yes", "no"),
-                        ]
-                    ],
-                    "convertor": lambda value: value == "yes",
+                    **YES_NO,
                 },
                 "discoverable": {
                     "prompt": "Is the device/service discoverable on the local network? (yes/no)",
                     "default": "no",
-                    "validators": [
-                        [
-                            "Type either 'yes' or 'no'",
-                            lambda value: value in ("yes", "no"),
-                        ]
-                    ],
-                    "convertor": lambda value: value == "yes",
+                    **YES_NO,
+                },
+                "oauth2": {
+                    "prompt": "Can the user authenticate the device using OAuth2? (yes/no)",
+                    "default": "no",
+                    **YES_NO,
                 },
             }
         )
-    )
-
-
-def gather_existing_integration() -> Info:
-    """Gather info about existing integration from user."""
-    answers = _gather_info(
-        {
-            "domain": {
-                "prompt": "What is the domain?",
-                "validators": [
-                    CHECK_EMPTY,
-                    [
-                        "Domains cannot contain spaces or special characters.",
-                        lambda value: value == slugify(value),
-                    ],
-                    [
-                        "This integration does not exist.",
-                        lambda value: (COMPONENT_DIR / value).exists(),
-                    ],
-                ],
-            }
-        }
-    )
 
-    return _load_existing_integration(answers["domain"])
+    return _gather_info(fields)
 
 
 def _load_existing_integration(domain) -> Info:
@@ -179,5 +160,4 @@ def _gather_info(fields) -> dict:
                     value = info["convertor"](value)
                 answers[key] = value
 
-    print()
     return answers
diff --git a/script/scaffold/generate.py b/script/scaffold/generate.py
index e16316fd76b129dce34884889a6c3bfc8aced7bb..a04cdb3ef5e0bd6cd254cf71cf6ac1210e6cede0 100644
--- a/script/scaffold/generate.py
+++ b/script/scaffold/generate.py
@@ -1,7 +1,6 @@
 """Generate an integration."""
 from pathlib import Path
 
-from .error import ExitApp
 from .model import Info
 
 TEMPLATE_DIR = Path(__file__).parent / "templates"
@@ -11,8 +10,6 @@ TEMPLATE_TESTS = TEMPLATE_DIR / "tests"
 
 def generate(template: str, info: Info) -> None:
     """Generate a template."""
-    _validate(template, info)
-
     print(f"Scaffolding {template} for the {info.domain} integration...")
     _ensure_tests_dir_exists(info)
     _generate(TEMPLATE_DIR / template / "integration", info.integration_dir, info)
@@ -21,13 +18,6 @@ def generate(template: str, info: Info) -> None:
     print()
 
 
-def _validate(template, info):
-    """Validate we can run this task."""
-    if template == "config_flow":
-        if (info.integration_dir / "config_flow.py").exists():
-            raise ExitApp(f"Integration {info.domain} already has a config flow.")
-
-
 def _generate(src_dir, target_dir, info: Info) -> None:
     """Generate an integration."""
     replaces = {"NEW_DOMAIN": info.domain, "NEW_NAME": info.name}
@@ -42,6 +32,20 @@ def _generate(src_dir, target_dir, info: Info) -> None:
             content = content.replace(to_search, to_replace)
 
         target_file = target_dir / source_file.relative_to(src_dir)
+
+        # If the target file exists, create our template as EXAMPLE_<filename>.
+        # Exception: If we are creating a new integration, we can end up running integration base
+        # and a config flows on top of one another. In that case, we want to override the files.
+        if not info.is_new and target_file.exists():
+            new_name = f"EXAMPLE_{target_file.name}"
+            print(f"File {target_file} already exists, creating {new_name} instead.")
+            target_file = target_file.parent / new_name
+            info.examples_added.add(target_file)
+        elif src_dir.name == "integration":
+            info.files_added.add(target_file)
+        else:
+            info.tests_added.add(target_file)
+
         print(f"Writing {target_file}")
         target_file.write_text(content)
 
@@ -58,6 +62,11 @@ def _ensure_tests_dir_exists(info: Info) -> None:
     )
 
 
+def _append(path: Path, text):
+    """Append some text to a path."""
+    path.write_text(path.read_text() + text)
+
+
 def _custom_tasks(template, info) -> None:
     """Handle custom tasks for templates."""
     if template == "integration":
@@ -68,7 +77,7 @@ def _custom_tasks(template, info) -> None:
 
         info.update_manifest(**changes)
 
-    if template == "device_trigger":
+    elif template == "device_trigger":
         info.update_strings(
             device_automation={
                 **info.strings().get("device_automation", {}),
@@ -79,7 +88,7 @@ def _custom_tasks(template, info) -> None:
             }
         )
 
-    if template == "device_condition":
+    elif template == "device_condition":
         info.update_strings(
             device_automation={
                 **info.strings().get("device_automation", {}),
@@ -90,7 +99,7 @@ def _custom_tasks(template, info) -> None:
             }
         )
 
-    if template == "device_action":
+    elif template == "device_action":
         info.update_strings(
             device_automation={
                 **info.strings().get("device_automation", {}),
@@ -101,7 +110,7 @@ def _custom_tasks(template, info) -> None:
             }
         )
 
-    if template == "config_flow":
+    elif template == "config_flow":
         info.update_manifest(config_flow=True)
         info.update_strings(
             config={
@@ -118,7 +127,7 @@ def _custom_tasks(template, info) -> None:
             }
         )
 
-    if template == "config_flow_discovery":
+    elif template == "config_flow_discovery":
         info.update_manifest(config_flow=True)
         info.update_strings(
             config={
@@ -136,19 +145,28 @@ def _custom_tasks(template, info) -> None:
             }
         )
 
-    if template in ("config_flow", "config_flow_discovery"):
-        init_file = info.integration_dir / "__init__.py"
-        init_file.write_text(
-            init_file.read_text()
-            + """
-
-async def async_setup_entry(hass, entry):
-    \"\"\"Set up a config entry for NEW_NAME.\"\"\"
-    # TODO forward the entry for each platform that you want to set up.
-    # hass.async_create_task(
-    #     hass.config_entries.async_forward_entry_setup(entry, "media_player")
-    # )
-
-    return True
-"""
+    elif template == "config_flow_oauth2":
+        info.update_manifest(config_flow=True)
+        info.update_strings(
+            config={
+                "title": info.name,
+                "step": {
+                    "pick_implementation": {"title": "Pick Authentication Method"}
+                },
+                "abort": {
+                    "missing_configuration": "The Somfy component is not configured. Please follow the documentation."
+                },
+                "create_entry": {
+                    "default": f"Successfully authenticated with {info.name}."
+                },
+            }
+        )
+        _append(
+            info.integration_dir / "const.py",
+            """
+
+# TODO Update with your own urls
+OAUTH2_AUTHORIZE = "https://www.example.com/auth/authorize"
+OAUTH2_TOKEN = "https://www.example.com/auth/token"
+""",
         )
diff --git a/script/scaffold/model.py b/script/scaffold/model.py
index 68ab771122e0ab0479f274191c7ebf6b445eb197..bfbcfa52544958d1f88c4bd5db4258ff163cfff9 100644
--- a/script/scaffold/model.py
+++ b/script/scaffold/model.py
@@ -1,6 +1,7 @@
 """Models for scaffolding."""
 import json
 from pathlib import Path
+from typing import Set
 
 import attr
 
@@ -13,10 +14,16 @@ class Info:
 
     domain: str = attr.ib()
     name: str = attr.ib()
+    is_new: bool = attr.ib()
     codeowner: str = attr.ib(default=None)
     requirement: str = attr.ib(default=None)
     authentication: str = attr.ib(default=None)
     discoverable: str = attr.ib(default=None)
+    oauth2: str = attr.ib(default=None)
+
+    files_added: Set[Path] = attr.ib(factory=set)
+    tests_added: Set[Path] = attr.ib(factory=set)
+    examples_added: Set[Path] = attr.ib(factory=set)
 
     @property
     def integration_dir(self) -> Path:
diff --git a/script/scaffold/templates/config_flow/integration/__init__.py b/script/scaffold/templates/config_flow/integration/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..403453a1f6b4e6f7ab59f3c33992bc86d4082e0d
--- /dev/null
+++ b/script/scaffold/templates/config_flow/integration/__init__.py
@@ -0,0 +1,49 @@
+"""The NEW_NAME integration."""
+import asyncio
+
+import voluptuous as vol
+
+from homeassistant.core import HomeAssistant
+from homeassistant.config_entries import ConfigEntry
+
+from .const import DOMAIN
+
+CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
+
+# TODO List the platforms that you want to support.
+# For your initial PR, limit it to 1 platform.
+PLATFORMS = ["light"]
+
+
+async def async_setup(hass: HomeAssistant, config: dict):
+    """Set up the NEW_NAME component."""
+    return True
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Set up Somfy from a config entry."""
+    # TODO Store an API object for your platforms to access
+    # hass.data[DOMAIN][entry.entry_id] = MyApi(…)
+
+    for component in PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(entry, component)
+        )
+
+    return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Unload a config entry."""
+    unload_ok = all(
+        await asyncio.gather(
+            *[
+                hass.config_entries.async_forward_entry_unload(entry, component)
+                for component in PLATFORMS
+            ]
+        )
+    )
+    if unload_ok:
+        hass.data[DOMAIN].pop(entry.entry_id)
+
+    return unload_ok
diff --git a/script/scaffold/templates/config_flow_discovery/integration/__init__.py b/script/scaffold/templates/config_flow_discovery/integration/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..403453a1f6b4e6f7ab59f3c33992bc86d4082e0d
--- /dev/null
+++ b/script/scaffold/templates/config_flow_discovery/integration/__init__.py
@@ -0,0 +1,49 @@
+"""The NEW_NAME integration."""
+import asyncio
+
+import voluptuous as vol
+
+from homeassistant.core import HomeAssistant
+from homeassistant.config_entries import ConfigEntry
+
+from .const import DOMAIN
+
+CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
+
+# TODO List the platforms that you want to support.
+# For your initial PR, limit it to 1 platform.
+PLATFORMS = ["light"]
+
+
+async def async_setup(hass: HomeAssistant, config: dict):
+    """Set up the NEW_NAME component."""
+    return True
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Set up Somfy from a config entry."""
+    # TODO Store an API object for your platforms to access
+    # hass.data[DOMAIN][entry.entry_id] = MyApi(…)
+
+    for component in PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(entry, component)
+        )
+
+    return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Unload a config entry."""
+    unload_ok = all(
+        await asyncio.gather(
+            *[
+                hass.config_entries.async_forward_entry_unload(entry, component)
+                for component in PLATFORMS
+            ]
+        )
+    )
+    if unload_ok:
+        hass.data[DOMAIN].pop(entry.entry_id)
+
+    return unload_ok
diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..43b4c6f31cd2d9543e8b6f77b42424d76539a1f7
--- /dev/null
+++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py
@@ -0,0 +1,94 @@
+"""The NEW_NAME integration."""
+import asyncio
+
+import voluptuous as vol
+
+from homeassistant.core import HomeAssistant
+from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
+from homeassistant.helpers import (
+    config_validation as cv,
+    config_entry_oauth2_flow,
+    aiohttp_client,
+)
+from homeassistant.config_entries import ConfigEntry
+
+from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
+from . import api, config_flow
+
+CONFIG_SCHEMA = vol.Schema(
+    {
+        DOMAIN: vol.Schema(
+            {
+                vol.Required(CONF_CLIENT_ID): cv.string,
+                vol.Required(CONF_CLIENT_SECRET): cv.string,
+            }
+        )
+    },
+    extra=vol.ALLOW_EXTRA,
+)
+
+# TODO List the platforms that you want to support.
+# For your initial PR, limit it to 1 platform.
+PLATFORMS = ["light"]
+
+
+async def async_setup(hass: HomeAssistant, config: dict):
+    """Set up the NEW_NAME component."""
+    hass.data[DOMAIN] = {}
+
+    if DOMAIN not in config:
+        return True
+
+    config_flow.OAuth2FlowHandler.async_register_implementation(
+        hass,
+        config_entry_oauth2_flow.LocalOAuth2Implementation(
+            hass,
+            DOMAIN,
+            config[DOMAIN][CONF_CLIENT_ID],
+            config[DOMAIN][CONF_CLIENT_SECRET],
+            OAUTH2_AUTHORIZE,
+            OAUTH2_TOKEN,
+        ),
+    )
+
+    return True
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Set up Somfy from a config entry."""
+    implementation = await config_entry_oauth2_flow.async_get_config_entry_implementation(
+        hass, entry
+    )
+
+    session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
+
+    # If using a requests-based API lib
+    hass.data[DOMAIN][entry.entry_id] = api.ConfigEntryAuth(hass, entry, session)
+
+    # If using an aiohttp-based API lib
+    hass.data[DOMAIN][entry.entry_id] = api.AsyncConfigEntryAuth(
+        aiohttp_client.async_get_clientsession(hass), session
+    )
+
+    for component in PLATFORMS:
+        hass.async_create_task(
+            hass.config_entries.async_forward_entry_setup(entry, component)
+        )
+
+    return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
+    """Unload a config entry."""
+    unload_ok = all(
+        await asyncio.gather(
+            *[
+                hass.config_entries.async_forward_entry_unload(entry, component)
+                for component in PLATFORMS
+            ]
+        )
+    )
+    if unload_ok:
+        hass.data[DOMAIN].pop(entry.entry_id)
+
+    return unload_ok
diff --git a/script/scaffold/templates/config_flow_oauth2/integration/api.py b/script/scaffold/templates/config_flow_oauth2/integration/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5aa4a81ebe0f1a3cc3a880437c394bc4e88b00d
--- /dev/null
+++ b/script/scaffold/templates/config_flow_oauth2/integration/api.py
@@ -0,0 +1,58 @@
+"""API for NEW_NAME bound to HASS OAuth."""
+from asyncio import run_coroutine_threadsafe
+
+from aiohttp import ClientSession
+import my_pypi_package
+
+from homeassistant import core, config_entries
+from homeassistant.helpers import config_entry_oauth2_flow
+
+# TODO the following two API examples are based on our suggested best practices
+# for libraries using OAuth2 with requests or aiohttp. Delete the one you won't use.
+# For more info see the docs at <insert url>.
+
+
+class ConfigEntryAuth(my_pypi_package.AbstractAuth):
+    """Provide NEW_NAME authentication tied to an OAuth2 based config entry."""
+
+    def __init__(
+        self,
+        hass: core.HomeAssistant,
+        config_entry: config_entries.ConfigEntry,
+        implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation,
+    ):
+        """Initialize NEW_NAME Auth."""
+        self.hass = hass
+        self.config_entry = config_entry
+        self.session = config_entry_oauth2_flow.OAuth2Session(
+            hass, config_entry, implementation
+        )
+        super().__init__(self.session.token)
+
+    def refresh_tokens(self) -> dict:
+        """Refresh and return new NEW_NAME tokens using Home Assistant OAuth2 session."""
+        run_coroutine_threadsafe(
+            self.session.async_ensure_token_valid(), self.hass.loop
+        ).result()
+
+        return self.session.token
+
+
+class AsyncConfigEntryAuth(my_pypi_package.AbstractAuth):
+    """Provide NEW_NAME authentication tied to an OAuth2 based config entry."""
+
+    def __init__(
+        self,
+        websession: ClientSession,
+        oauth_session: config_entry_oauth2_flow.OAuth2Session,
+    ):
+        """Initialize NEW_NAME auth."""
+        super().__init__(websession)
+        self._oauth_session = oauth_session
+
+    async def async_get_access_token(self):
+        """Return a valid access token."""
+        if not self._oauth_session.is_valid:
+            await self._oauth_session.async_ensure_token_valid()
+
+        return self._oauth_session.token
diff --git a/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py b/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..1112a404e6087b45e1531cbff23ce9c39da4fcb4
--- /dev/null
+++ b/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py
@@ -0,0 +1,23 @@
+"""Config flow for NEW_NAME."""
+import logging
+
+from homeassistant import config_entries
+from homeassistant.helpers import config_entry_oauth2_flow
+from .const import DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class OAuth2FlowHandler(
+    config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
+):
+    """Config flow to handle NEW_NAME OAuth2 authentication."""
+
+    DOMAIN = DOMAIN
+    # TODO Pick one from config_entries.CONN_CLASS_*
+    CONNECTION_CLASS = config_entries.CONN_CLASS_UNKNOWN
+
+    @property
+    def logger(self) -> logging.Logger:
+        """Return logger."""
+        return logging.getLogger(__name__)
diff --git a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e61bcbfb1b91372906c78ce71fa7149afe41412
--- /dev/null
+++ b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py
@@ -0,0 +1,60 @@
+"""Test the NEW_NAME config flow."""
+from homeassistant import config_entries, setup, data_entry_flow
+from homeassistant.components.NEW_DOMAIN.const import (
+    DOMAIN,
+    OAUTH2_AUTHORIZE,
+    OAUTH2_TOKEN,
+)
+from homeassistant.helpers import config_entry_oauth2_flow
+
+CLIENT_ID = "1234"
+CLIENT_SECRET = "5678"
+
+
+async def test_full_flow(hass, aiohttp_client, aioclient_mock):
+    """Check full flow."""
+    assert await setup.async_setup_component(
+        hass,
+        "NEW_DOMAIN",
+        {
+            "NEW_DOMAIN": {
+                "type": "oauth2",
+                "client_id": CLIENT_ID,
+                "client_secret": CLIENT_SECRET,
+            },
+            "http": {"base_url": "https://example.com"},
+        },
+    )
+
+    result = await hass.config_entries.flow.async_init(
+        "NEW_DOMAIN", context={"source": config_entries.SOURCE_USER}
+    )
+    state = config_entry_oauth2_flow._encode_jwt(hass, {"flow_id": result["flow_id"]})
+
+    assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP
+    assert result["url"] == (
+        f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
+        "&redirect_uri=https://example.com/auth/external/callback"
+        f"&state={state}"
+    )
+
+    client = await aiohttp_client(hass.http.app)
+    resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
+    assert resp.status == 200
+    assert resp.headers["content-type"] == "text/html; charset=utf-8"
+
+    aioclient_mock.post(
+        OAUTH2_TOKEN,
+        json={
+            "refresh_token": "mock-refresh-token",
+            "access_token": "mock-access-token",
+            "type": "Bearer",
+            "expires_in": 60,
+        },
+    )
+
+    result = await hass.config_entries.flow.async_configure(result["flow_id"])
+
+    assert len(hass.config_entries.async_entries(DOMAIN)) == 1
+    entry = hass.config_entries.async_entries(DOMAIN)[0]
+    assert entry.data["type"] == "oauth2"
diff --git a/script/scaffold/templates/integration/integration/__init__.py b/script/scaffold/templates/integration/integration/__init__.py
index 7ab8b736782f62b7bb4993820ac9518f1729c439..c2ae59aaad44178b3257204b466eb4e41b7ce979 100644
--- a/script/scaffold/templates/integration/integration/__init__.py
+++ b/script/scaffold/templates/integration/integration/__init__.py
@@ -1,12 +1,14 @@
 """The NEW_NAME integration."""
 import voluptuous as vol
 
+from homeassistant.core import HomeAssistant
+
 from .const import DOMAIN
 
 
-CONFIG_SCHEMA = vol.Schema({vol.Optional(DOMAIN): {}})
+CONFIG_SCHEMA = vol.Schema({vol.Optional(DOMAIN): {}}, extra=vol.ALLOW_EXTRA)
 
 
-async def async_setup(hass, config):
+async def async_setup(hass: HomeAssistant, config: dict):
     """Set up the NEW_NAME integration."""
     return True