diff --git a/homeassistant/auth.py b/homeassistant/auth.py
deleted file mode 100644
index c84f5e83ef0aa57aa85927ac66dabf0e49a642ea..0000000000000000000000000000000000000000
--- a/homeassistant/auth.py
+++ /dev/null
@@ -1,613 +0,0 @@
-"""Provide an authentication layer for Home Assistant."""
-import asyncio
-import binascii
-import importlib
-import logging
-import os
-import uuid
-from collections import OrderedDict
-from datetime import datetime, timedelta
-
-import attr
-import voluptuous as vol
-from voluptuous.humanize import humanize_error
-
-from homeassistant import data_entry_flow, requirements
-from homeassistant.const import CONF_TYPE, CONF_NAME, CONF_ID
-from homeassistant.core import callback
-from homeassistant.util import dt as dt_util
-from homeassistant.util.decorator import Registry
-
-_LOGGER = logging.getLogger(__name__)
-
-STORAGE_VERSION = 1
-STORAGE_KEY = 'auth'
-
-AUTH_PROVIDERS = Registry()
-
-AUTH_PROVIDER_SCHEMA = vol.Schema({
-    vol.Required(CONF_TYPE): str,
-    vol.Optional(CONF_NAME): str,
-    # Specify ID if you have two auth providers for same type.
-    vol.Optional(CONF_ID): str,
-}, extra=vol.ALLOW_EXTRA)
-
-ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
-DATA_REQS = 'auth_reqs_processed'
-
-
-def generate_secret(entropy: int = 32) -> str:
-    """Generate a secret.
-
-    Backport of secrets.token_hex from Python 3.6
-
-    Event loop friendly.
-    """
-    return binascii.hexlify(os.urandom(entropy)).decode('ascii')
-
-
-class AuthProvider:
-    """Provider of user authentication."""
-
-    DEFAULT_TITLE = 'Unnamed auth provider'
-
-    initialized = False
-
-    def __init__(self, hass, store, config):
-        """Initialize an auth provider."""
-        self.hass = hass
-        self.store = store
-        self.config = config
-
-    @property
-    def id(self):  # pylint: disable=invalid-name
-        """Return id of the auth provider.
-
-        Optional, can be None.
-        """
-        return self.config.get(CONF_ID)
-
-    @property
-    def type(self):
-        """Return type of the provider."""
-        return self.config[CONF_TYPE]
-
-    @property
-    def name(self):
-        """Return the name of the auth provider."""
-        return self.config.get(CONF_NAME, self.DEFAULT_TITLE)
-
-    async def async_credentials(self):
-        """Return all credentials of this provider."""
-        users = await self.store.async_get_users()
-        return [
-            credentials
-            for user in users
-            for credentials in user.credentials
-            if (credentials.auth_provider_type == self.type and
-                credentials.auth_provider_id == self.id)
-        ]
-
-    @callback
-    def async_create_credentials(self, data):
-        """Create credentials."""
-        return Credentials(
-            auth_provider_type=self.type,
-            auth_provider_id=self.id,
-            data=data,
-        )
-
-    # Implement by extending class
-
-    async def async_initialize(self):
-        """Initialize the auth provider.
-
-        Optional.
-        """
-
-    async def async_credential_flow(self):
-        """Return the data flow for logging in with auth provider."""
-        raise NotImplementedError
-
-    async def async_get_or_create_credentials(self, flow_result):
-        """Get credentials based on the flow result."""
-        raise NotImplementedError
-
-    async def async_user_meta_for_credentials(self, credentials):
-        """Return extra user metadata for credentials.
-
-        Will be used to populate info when creating a new user.
-        """
-        return {}
-
-
-@attr.s(slots=True)
-class User:
-    """A user."""
-
-    name = attr.ib(type=str)
-    id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
-    is_owner = attr.ib(type=bool, default=False)
-    is_active = attr.ib(type=bool, default=False)
-    system_generated = attr.ib(type=bool, default=False)
-
-    # List of credentials of a user.
-    credentials = attr.ib(type=list, default=attr.Factory(list), cmp=False)
-
-    # Tokens associated with a user.
-    refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict), cmp=False)
-
-
-@attr.s(slots=True)
-class RefreshToken:
-    """RefreshToken for a user to grant new access tokens."""
-
-    user = attr.ib(type=User)
-    client_id = attr.ib(type=str)
-    id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
-    created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
-    access_token_expiration = attr.ib(type=timedelta,
-                                      default=ACCESS_TOKEN_EXPIRATION)
-    token = attr.ib(type=str,
-                    default=attr.Factory(lambda: generate_secret(64)))
-    access_tokens = attr.ib(type=list, default=attr.Factory(list), cmp=False)
-
-
-@attr.s(slots=True)
-class AccessToken:
-    """Access token to access the API.
-
-    These will only ever be stored in memory and not be persisted.
-    """
-
-    refresh_token = attr.ib(type=RefreshToken)
-    created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
-    token = attr.ib(type=str,
-                    default=attr.Factory(generate_secret))
-
-    @property
-    def expired(self):
-        """Return if this token has expired."""
-        expires = self.created_at + self.refresh_token.access_token_expiration
-        return dt_util.utcnow() > expires
-
-
-@attr.s(slots=True)
-class Credentials:
-    """Credentials for a user on an auth provider."""
-
-    auth_provider_type = attr.ib(type=str)
-    auth_provider_id = attr.ib(type=str)
-
-    # Allow the auth provider to store data to represent their auth.
-    data = attr.ib(type=dict)
-
-    id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
-    is_new = attr.ib(type=bool, default=True)
-
-
-async def load_auth_provider_module(hass, provider):
-    """Load an auth provider."""
-    try:
-        module = importlib.import_module(
-            'homeassistant.auth_providers.{}'.format(provider))
-    except ImportError:
-        _LOGGER.warning('Unable to find auth provider %s', provider)
-        return None
-
-    if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'):
-        return module
-
-    processed = hass.data.get(DATA_REQS)
-
-    if processed is None:
-        processed = hass.data[DATA_REQS] = set()
-    elif provider in processed:
-        return module
-
-    req_success = await requirements.async_process_requirements(
-        hass, 'auth provider {}'.format(provider), module.REQUIREMENTS)
-
-    if not req_success:
-        return None
-
-    return module
-
-
-async def auth_manager_from_config(hass, provider_configs):
-    """Initialize an auth manager from config."""
-    store = AuthStore(hass)
-    if provider_configs:
-        providers = await asyncio.gather(
-            *[_auth_provider_from_config(hass, store, config)
-              for config in provider_configs])
-    else:
-        providers = []
-    # So returned auth providers are in same order as config
-    provider_hash = OrderedDict()
-    for provider in providers:
-        if provider is None:
-            continue
-
-        key = (provider.type, provider.id)
-
-        if key in provider_hash:
-            _LOGGER.error(
-                'Found duplicate provider: %s. Please add unique IDs if you '
-                'want to have the same provider twice.', key)
-            continue
-
-        provider_hash[key] = provider
-    manager = AuthManager(hass, store, provider_hash)
-    return manager
-
-
-async def _auth_provider_from_config(hass, store, config):
-    """Initialize an auth provider from a config."""
-    provider_name = config[CONF_TYPE]
-    module = await load_auth_provider_module(hass, provider_name)
-
-    if module is None:
-        return None
-
-    try:
-        config = module.CONFIG_SCHEMA(config)
-    except vol.Invalid as err:
-        _LOGGER.error('Invalid configuration for auth provider %s: %s',
-                      provider_name, humanize_error(config, err))
-        return None
-
-    return AUTH_PROVIDERS[provider_name](hass, store, config)
-
-
-class AuthManager:
-    """Manage the authentication for Home Assistant."""
-
-    def __init__(self, hass, store, providers):
-        """Initialize the auth manager."""
-        self._store = store
-        self._providers = providers
-        self.login_flow = data_entry_flow.FlowManager(
-            hass, self._async_create_login_flow,
-            self._async_finish_login_flow)
-        self._access_tokens = {}
-
-    @property
-    def active(self):
-        """Return if any auth providers are registered."""
-        return bool(self._providers)
-
-    @property
-    def support_legacy(self):
-        """
-        Return if legacy_api_password auth providers are registered.
-
-        Should be removed when we removed legacy_api_password auth providers.
-        """
-        for provider_type, _ in self._providers:
-            if provider_type == 'legacy_api_password':
-                return True
-        return False
-
-    @property
-    def async_auth_providers(self):
-        """Return a list of available auth providers."""
-        return self._providers.values()
-
-    async def async_get_user(self, user_id):
-        """Retrieve a user."""
-        return await self._store.async_get_user(user_id)
-
-    async def async_create_system_user(self, name):
-        """Create a system user."""
-        return await self._store.async_create_user(
-            name=name,
-            system_generated=True,
-            is_active=True,
-        )
-
-    async def async_get_or_create_user(self, credentials):
-        """Get or create a user."""
-        if not credentials.is_new:
-            for user in await self._store.async_get_users():
-                for creds in user.credentials:
-                    if creds.id == credentials.id:
-                        return user
-
-            raise ValueError('Unable to find the user.')
-
-        auth_provider = self._async_get_auth_provider(credentials)
-        info = await auth_provider.async_user_meta_for_credentials(
-            credentials)
-
-        kwargs = {
-            'credentials': credentials,
-            'name': info.get('name')
-        }
-
-        # Make owner and activate user if it's the first user.
-        if await self._store.async_get_users():
-            kwargs['is_owner'] = False
-            kwargs['is_active'] = False
-        else:
-            kwargs['is_owner'] = True
-            kwargs['is_active'] = True
-
-        return await self._store.async_create_user(**kwargs)
-
-    async def async_link_user(self, user, credentials):
-        """Link credentials to an existing user."""
-        await self._store.async_link_user(user, credentials)
-
-    async def async_remove_user(self, user):
-        """Remove a user."""
-        await self._store.async_remove_user(user)
-
-    async def async_create_refresh_token(self, user, client_id=None):
-        """Create a new refresh token for a user."""
-        if not user.is_active:
-            raise ValueError('User is not active')
-
-        if user.system_generated and client_id is not None:
-            raise ValueError(
-                'System generated users cannot have refresh tokens connected '
-                'to a client.')
-
-        if not user.system_generated and client_id is None:
-            raise ValueError('Client is required to generate a refresh token.')
-
-        return await self._store.async_create_refresh_token(user, client_id)
-
-    async def async_get_refresh_token(self, token):
-        """Get refresh token by token."""
-        return await self._store.async_get_refresh_token(token)
-
-    @callback
-    def async_create_access_token(self, refresh_token):
-        """Create a new access token."""
-        access_token = AccessToken(refresh_token=refresh_token)
-        self._access_tokens[access_token.token] = access_token
-        return access_token
-
-    @callback
-    def async_get_access_token(self, token):
-        """Get an access token."""
-        tkn = self._access_tokens.get(token)
-
-        if tkn is None:
-            return None
-
-        if tkn.expired:
-            self._access_tokens.pop(token)
-            return None
-
-        return tkn
-
-    async def _async_create_login_flow(self, handler, *, source, data):
-        """Create a login flow."""
-        auth_provider = self._providers[handler]
-
-        if not auth_provider.initialized:
-            auth_provider.initialized = True
-            await auth_provider.async_initialize()
-
-        return await auth_provider.async_credential_flow()
-
-    async def _async_finish_login_flow(self, result):
-        """Result of a credential login flow."""
-        if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
-            return None
-
-        auth_provider = self._providers[result['handler']]
-        return await auth_provider.async_get_or_create_credentials(
-            result['data'])
-
-    @callback
-    def _async_get_auth_provider(self, credentials):
-        """Helper to get auth provider from a set of credentials."""
-        auth_provider_key = (credentials.auth_provider_type,
-                             credentials.auth_provider_id)
-        return self._providers[auth_provider_key]
-
-
-class AuthStore:
-    """Stores authentication info.
-
-    Any mutation to an object should happen inside the auth store.
-
-    The auth store is lazy. It won't load the data from disk until a method is
-    called that needs it.
-    """
-
-    def __init__(self, hass):
-        """Initialize the auth store."""
-        self.hass = hass
-        self._users = None
-        self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
-
-    async def async_get_users(self):
-        """Retrieve all users."""
-        if self._users is None:
-            await self.async_load()
-
-        return list(self._users.values())
-
-    async def async_get_user(self, user_id):
-        """Retrieve a user by id."""
-        if self._users is None:
-            await self.async_load()
-
-        return self._users.get(user_id)
-
-    async def async_create_user(self, name, is_owner=None, is_active=None,
-                                system_generated=None, credentials=None):
-        """Create a new user."""
-        if self._users is None:
-            await self.async_load()
-
-        kwargs = {
-            'name': name
-        }
-
-        if is_owner is not None:
-            kwargs['is_owner'] = is_owner
-
-        if is_active is not None:
-            kwargs['is_active'] = is_active
-
-        if system_generated is not None:
-            kwargs['system_generated'] = system_generated
-
-        new_user = User(**kwargs)
-
-        self._users[new_user.id] = new_user
-
-        if credentials is None:
-            await self.async_save()
-            return new_user
-
-        # Saving is done inside the link.
-        await self.async_link_user(new_user, credentials)
-        return new_user
-
-    async def async_link_user(self, user, credentials):
-        """Add credentials to an existing user."""
-        user.credentials.append(credentials)
-        await self.async_save()
-        credentials.is_new = False
-
-    async def async_remove_user(self, user):
-        """Remove a user."""
-        self._users.pop(user.id)
-        await self.async_save()
-
-    async def async_create_refresh_token(self, user, client_id=None):
-        """Create a new token for a user."""
-        refresh_token = RefreshToken(user=user, client_id=client_id)
-        user.refresh_tokens[refresh_token.token] = refresh_token
-        await self.async_save()
-        return refresh_token
-
-    async def async_get_refresh_token(self, token):
-        """Get refresh token by token."""
-        if self._users is None:
-            await self.async_load()
-
-        for user in self._users.values():
-            refresh_token = user.refresh_tokens.get(token)
-            if refresh_token is not None:
-                return refresh_token
-
-        return None
-
-    async def async_load(self):
-        """Load the users."""
-        data = await self._store.async_load()
-
-        # Make sure that we're not overriding data if 2 loads happened at the
-        # same time
-        if self._users is not None:
-            return
-
-        if data is None:
-            self._users = {}
-            return
-
-        users = {
-            user_dict['id']: User(**user_dict) for user_dict in data['users']
-        }
-
-        for cred_dict in data['credentials']:
-            users[cred_dict['user_id']].credentials.append(Credentials(
-                id=cred_dict['id'],
-                is_new=False,
-                auth_provider_type=cred_dict['auth_provider_type'],
-                auth_provider_id=cred_dict['auth_provider_id'],
-                data=cred_dict['data'],
-            ))
-
-        refresh_tokens = {}
-
-        for rt_dict in data['refresh_tokens']:
-            token = RefreshToken(
-                id=rt_dict['id'],
-                user=users[rt_dict['user_id']],
-                client_id=rt_dict['client_id'],
-                created_at=dt_util.parse_datetime(rt_dict['created_at']),
-                access_token_expiration=timedelta(
-                    seconds=rt_dict['access_token_expiration']),
-                token=rt_dict['token'],
-            )
-            refresh_tokens[token.id] = token
-            users[rt_dict['user_id']].refresh_tokens[token.token] = token
-
-        for ac_dict in data['access_tokens']:
-            refresh_token = refresh_tokens[ac_dict['refresh_token_id']]
-            token = AccessToken(
-                refresh_token=refresh_token,
-                created_at=dt_util.parse_datetime(ac_dict['created_at']),
-                token=ac_dict['token'],
-            )
-            refresh_token.access_tokens.append(token)
-
-        self._users = users
-
-    async def async_save(self):
-        """Save users."""
-        users = [
-            {
-                'id': user.id,
-                'is_owner': user.is_owner,
-                'is_active': user.is_active,
-                'name': user.name,
-                'system_generated': user.system_generated,
-            }
-            for user in self._users.values()
-        ]
-
-        credentials = [
-            {
-                'id': credential.id,
-                'user_id': user.id,
-                'auth_provider_type': credential.auth_provider_type,
-                'auth_provider_id': credential.auth_provider_id,
-                'data': credential.data,
-            }
-            for user in self._users.values()
-            for credential in user.credentials
-        ]
-
-        refresh_tokens = [
-            {
-                'id': refresh_token.id,
-                'user_id': user.id,
-                'client_id': refresh_token.client_id,
-                'created_at': refresh_token.created_at.isoformat(),
-                'access_token_expiration':
-                    refresh_token.access_token_expiration.total_seconds(),
-                'token': refresh_token.token,
-            }
-            for user in self._users.values()
-            for refresh_token in user.refresh_tokens.values()
-        ]
-
-        access_tokens = [
-            {
-                'id': user.id,
-                'refresh_token_id': refresh_token.id,
-                'created_at': access_token.created_at.isoformat(),
-                'token': access_token.token,
-            }
-            for user in self._users.values()
-            for refresh_token in user.refresh_tokens.values()
-            for access_token in refresh_token.access_tokens
-        ]
-
-        data = {
-            'users': users,
-            'credentials': credentials,
-            'access_tokens': access_tokens,
-            'refresh_tokens': refresh_tokens,
-        }
-
-        await self._store.async_save(data, delay=1)
diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5db65586b1caf4035223923d63b6cfc5742b584
--- /dev/null
+++ b/homeassistant/auth/__init__.py
@@ -0,0 +1,191 @@
+"""Provide an authentication layer for Home Assistant."""
+import asyncio
+import logging
+from collections import OrderedDict
+
+from homeassistant import data_entry_flow
+from homeassistant.core import callback
+
+from . import models
+from . import auth_store
+from .providers import auth_provider_from_config
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def auth_manager_from_config(hass, provider_configs):
+    """Initialize an auth manager from config."""
+    store = auth_store.AuthStore(hass)
+    if provider_configs:
+        providers = await asyncio.gather(
+            *[auth_provider_from_config(hass, store, config)
+              for config in provider_configs])
+    else:
+        providers = []
+    # So returned auth providers are in same order as config
+    provider_hash = OrderedDict()
+    for provider in providers:
+        if provider is None:
+            continue
+
+        key = (provider.type, provider.id)
+
+        if key in provider_hash:
+            _LOGGER.error(
+                'Found duplicate provider: %s. Please add unique IDs if you '
+                'want to have the same provider twice.', key)
+            continue
+
+        provider_hash[key] = provider
+    manager = AuthManager(hass, store, provider_hash)
+    return manager
+
+
+class AuthManager:
+    """Manage the authentication for Home Assistant."""
+
+    def __init__(self, hass, store, providers):
+        """Initialize the auth manager."""
+        self._store = store
+        self._providers = providers
+        self.login_flow = data_entry_flow.FlowManager(
+            hass, self._async_create_login_flow,
+            self._async_finish_login_flow)
+        self._access_tokens = {}
+
+    @property
+    def active(self):
+        """Return if any auth providers are registered."""
+        return bool(self._providers)
+
+    @property
+    def support_legacy(self):
+        """
+        Return if legacy_api_password auth providers are registered.
+
+        Should be removed when we removed legacy_api_password auth providers.
+        """
+        for provider_type, _ in self._providers:
+            if provider_type == 'legacy_api_password':
+                return True
+        return False
+
+    @property
+    def async_auth_providers(self):
+        """Return a list of available auth providers."""
+        return self._providers.values()
+
+    async def async_get_user(self, user_id):
+        """Retrieve a user."""
+        return await self._store.async_get_user(user_id)
+
+    async def async_create_system_user(self, name):
+        """Create a system user."""
+        return await self._store.async_create_user(
+            name=name,
+            system_generated=True,
+            is_active=True,
+        )
+
+    async def async_get_or_create_user(self, credentials):
+        """Get or create a user."""
+        if not credentials.is_new:
+            for user in await self._store.async_get_users():
+                for creds in user.credentials:
+                    if creds.id == credentials.id:
+                        return user
+
+            raise ValueError('Unable to find the user.')
+
+        auth_provider = self._async_get_auth_provider(credentials)
+        info = await auth_provider.async_user_meta_for_credentials(
+            credentials)
+
+        kwargs = {
+            'credentials': credentials,
+            'name': info.get('name')
+        }
+
+        # Make owner and activate user if it's the first user.
+        if await self._store.async_get_users():
+            kwargs['is_owner'] = False
+            kwargs['is_active'] = False
+        else:
+            kwargs['is_owner'] = True
+            kwargs['is_active'] = True
+
+        return await self._store.async_create_user(**kwargs)
+
+    async def async_link_user(self, user, credentials):
+        """Link credentials to an existing user."""
+        await self._store.async_link_user(user, credentials)
+
+    async def async_remove_user(self, user):
+        """Remove a user."""
+        await self._store.async_remove_user(user)
+
+    async def async_create_refresh_token(self, user, client_id=None):
+        """Create a new refresh token for a user."""
+        if not user.is_active:
+            raise ValueError('User is not active')
+
+        if user.system_generated and client_id is not None:
+            raise ValueError(
+                'System generated users cannot have refresh tokens connected '
+                'to a client.')
+
+        if not user.system_generated and client_id is None:
+            raise ValueError('Client is required to generate a refresh token.')
+
+        return await self._store.async_create_refresh_token(user, client_id)
+
+    async def async_get_refresh_token(self, token):
+        """Get refresh token by token."""
+        return await self._store.async_get_refresh_token(token)
+
+    @callback
+    def async_create_access_token(self, refresh_token):
+        """Create a new access token."""
+        access_token = models.AccessToken(refresh_token=refresh_token)
+        self._access_tokens[access_token.token] = access_token
+        return access_token
+
+    @callback
+    def async_get_access_token(self, token):
+        """Get an access token."""
+        tkn = self._access_tokens.get(token)
+
+        if tkn is None:
+            return None
+
+        if tkn.expired:
+            self._access_tokens.pop(token)
+            return None
+
+        return tkn
+
+    async def _async_create_login_flow(self, handler, *, source, data):
+        """Create a login flow."""
+        auth_provider = self._providers[handler]
+
+        if not auth_provider.initialized:
+            auth_provider.initialized = True
+            await auth_provider.async_initialize()
+
+        return await auth_provider.async_credential_flow()
+
+    async def _async_finish_login_flow(self, result):
+        """Result of a credential login flow."""
+        if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
+            return None
+
+        auth_provider = self._providers[result['handler']]
+        return await auth_provider.async_get_or_create_credentials(
+            result['data'])
+
+    @callback
+    def _async_get_auth_provider(self, credentials):
+        """Helper to get auth provider from a set of credentials."""
+        auth_provider_key = (credentials.auth_provider_type,
+                             credentials.auth_provider_id)
+        return self._providers[auth_provider_key]
diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py
new file mode 100644
index 0000000000000000000000000000000000000000..691e561f22ff5c60bacb942f66f702745a1004ba
--- /dev/null
+++ b/homeassistant/auth/auth_store.py
@@ -0,0 +1,213 @@
+"""Storage for auth models."""
+from datetime import timedelta
+
+from homeassistant.util import dt as dt_util
+
+from . import models
+
+STORAGE_VERSION = 1
+STORAGE_KEY = 'auth'
+
+
+class AuthStore:
+    """Stores authentication info.
+
+    Any mutation to an object should happen inside the auth store.
+
+    The auth store is lazy. It won't load the data from disk until a method is
+    called that needs it.
+    """
+
+    def __init__(self, hass):
+        """Initialize the auth store."""
+        self.hass = hass
+        self._users = None
+        self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
+
+    async def async_get_users(self):
+        """Retrieve all users."""
+        if self._users is None:
+            await self.async_load()
+
+        return list(self._users.values())
+
+    async def async_get_user(self, user_id):
+        """Retrieve a user by id."""
+        if self._users is None:
+            await self.async_load()
+
+        return self._users.get(user_id)
+
+    async def async_create_user(self, name, is_owner=None, is_active=None,
+                                system_generated=None, credentials=None):
+        """Create a new user."""
+        if self._users is None:
+            await self.async_load()
+
+        kwargs = {
+            'name': name
+        }
+
+        if is_owner is not None:
+            kwargs['is_owner'] = is_owner
+
+        if is_active is not None:
+            kwargs['is_active'] = is_active
+
+        if system_generated is not None:
+            kwargs['system_generated'] = system_generated
+
+        new_user = models.User(**kwargs)
+
+        self._users[new_user.id] = new_user
+
+        if credentials is None:
+            await self.async_save()
+            return new_user
+
+        # Saving is done inside the link.
+        await self.async_link_user(new_user, credentials)
+        return new_user
+
+    async def async_link_user(self, user, credentials):
+        """Add credentials to an existing user."""
+        user.credentials.append(credentials)
+        await self.async_save()
+        credentials.is_new = False
+
+    async def async_remove_user(self, user):
+        """Remove a user."""
+        self._users.pop(user.id)
+        await self.async_save()
+
+    async def async_create_refresh_token(self, user, client_id=None):
+        """Create a new token for a user."""
+        refresh_token = models.RefreshToken(user=user, client_id=client_id)
+        user.refresh_tokens[refresh_token.token] = refresh_token
+        await self.async_save()
+        return refresh_token
+
+    async def async_get_refresh_token(self, token):
+        """Get refresh token by token."""
+        if self._users is None:
+            await self.async_load()
+
+        for user in self._users.values():
+            refresh_token = user.refresh_tokens.get(token)
+            if refresh_token is not None:
+                return refresh_token
+
+        return None
+
+    async def async_load(self):
+        """Load the users."""
+        data = await self._store.async_load()
+
+        # Make sure that we're not overriding data if 2 loads happened at the
+        # same time
+        if self._users is not None:
+            return
+
+        if data is None:
+            self._users = {}
+            return
+
+        users = {
+            user_dict['id']: models.User(**user_dict)
+            for user_dict in data['users']
+        }
+
+        for cred_dict in data['credentials']:
+            users[cred_dict['user_id']].credentials.append(models.Credentials(
+                id=cred_dict['id'],
+                is_new=False,
+                auth_provider_type=cred_dict['auth_provider_type'],
+                auth_provider_id=cred_dict['auth_provider_id'],
+                data=cred_dict['data'],
+            ))
+
+        refresh_tokens = {}
+
+        for rt_dict in data['refresh_tokens']:
+            token = models.RefreshToken(
+                id=rt_dict['id'],
+                user=users[rt_dict['user_id']],
+                client_id=rt_dict['client_id'],
+                created_at=dt_util.parse_datetime(rt_dict['created_at']),
+                access_token_expiration=timedelta(
+                    seconds=rt_dict['access_token_expiration']),
+                token=rt_dict['token'],
+            )
+            refresh_tokens[token.id] = token
+            users[rt_dict['user_id']].refresh_tokens[token.token] = token
+
+        for ac_dict in data['access_tokens']:
+            refresh_token = refresh_tokens[ac_dict['refresh_token_id']]
+            token = models.AccessToken(
+                refresh_token=refresh_token,
+                created_at=dt_util.parse_datetime(ac_dict['created_at']),
+                token=ac_dict['token'],
+            )
+            refresh_token.access_tokens.append(token)
+
+        self._users = users
+
+    async def async_save(self):
+        """Save users."""
+        users = [
+            {
+                'id': user.id,
+                'is_owner': user.is_owner,
+                'is_active': user.is_active,
+                'name': user.name,
+                'system_generated': user.system_generated,
+            }
+            for user in self._users.values()
+        ]
+
+        credentials = [
+            {
+                'id': credential.id,
+                'user_id': user.id,
+                'auth_provider_type': credential.auth_provider_type,
+                'auth_provider_id': credential.auth_provider_id,
+                'data': credential.data,
+            }
+            for user in self._users.values()
+            for credential in user.credentials
+        ]
+
+        refresh_tokens = [
+            {
+                'id': refresh_token.id,
+                'user_id': user.id,
+                'client_id': refresh_token.client_id,
+                'created_at': refresh_token.created_at.isoformat(),
+                'access_token_expiration':
+                    refresh_token.access_token_expiration.total_seconds(),
+                'token': refresh_token.token,
+            }
+            for user in self._users.values()
+            for refresh_token in user.refresh_tokens.values()
+        ]
+
+        access_tokens = [
+            {
+                'id': user.id,
+                'refresh_token_id': refresh_token.id,
+                'created_at': access_token.created_at.isoformat(),
+                'token': access_token.token,
+            }
+            for user in self._users.values()
+            for refresh_token in user.refresh_tokens.values()
+            for access_token in refresh_token.access_tokens
+        ]
+
+        data = {
+            'users': users,
+            'credentials': credentials,
+            'access_tokens': access_tokens,
+            'refresh_tokens': refresh_tokens,
+        }
+
+        await self._store.async_save(data, delay=1)
diff --git a/homeassistant/auth/const.py b/homeassistant/auth/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..082d8966275670612390dc3b3b7218d898247137
--- /dev/null
+++ b/homeassistant/auth/const.py
@@ -0,0 +1,4 @@
+"""Constants for the auth module."""
+from datetime import timedelta
+
+ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..38e054dc7cf71aa0d4b6714649ddb3d4434205d3
--- /dev/null
+++ b/homeassistant/auth/models.py
@@ -0,0 +1,75 @@
+"""Auth models."""
+from datetime import datetime, timedelta
+import uuid
+
+import attr
+
+from homeassistant.util import dt as dt_util
+
+from .const import ACCESS_TOKEN_EXPIRATION
+from .util import generate_secret
+
+
+@attr.s(slots=True)
+class User:
+    """A user."""
+
+    name = attr.ib(type=str)
+    id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
+    is_owner = attr.ib(type=bool, default=False)
+    is_active = attr.ib(type=bool, default=False)
+    system_generated = attr.ib(type=bool, default=False)
+
+    # List of credentials of a user.
+    credentials = attr.ib(type=list, default=attr.Factory(list), cmp=False)
+
+    # Tokens associated with a user.
+    refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict), cmp=False)
+
+
+@attr.s(slots=True)
+class RefreshToken:
+    """RefreshToken for a user to grant new access tokens."""
+
+    user = attr.ib(type=User)
+    client_id = attr.ib(type=str)
+    id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
+    created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
+    access_token_expiration = attr.ib(type=timedelta,
+                                      default=ACCESS_TOKEN_EXPIRATION)
+    token = attr.ib(type=str,
+                    default=attr.Factory(lambda: generate_secret(64)))
+    access_tokens = attr.ib(type=list, default=attr.Factory(list), cmp=False)
+
+
+@attr.s(slots=True)
+class AccessToken:
+    """Access token to access the API.
+
+    These will only ever be stored in memory and not be persisted.
+    """
+
+    refresh_token = attr.ib(type=RefreshToken)
+    created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
+    token = attr.ib(type=str,
+                    default=attr.Factory(generate_secret))
+
+    @property
+    def expired(self):
+        """Return if this token has expired."""
+        expires = self.created_at + self.refresh_token.access_token_expiration
+        return dt_util.utcnow() > expires
+
+
+@attr.s(slots=True)
+class Credentials:
+    """Credentials for a user on an auth provider."""
+
+    auth_provider_type = attr.ib(type=str)
+    auth_provider_id = attr.ib(type=str)
+
+    # Allow the auth provider to store data to represent their auth.
+    data = attr.ib(type=dict)
+
+    id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
+    is_new = attr.ib(type=bool, default=True)
diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6630383ff21598e26e0cc4f3a67e36573155f11
--- /dev/null
+++ b/homeassistant/auth/providers/__init__.py
@@ -0,0 +1,147 @@
+"""Auth providers for Home Assistant."""
+import importlib
+import logging
+
+import voluptuous as vol
+from voluptuous.humanize import humanize_error
+
+from homeassistant import requirements
+from homeassistant.core import callback
+from homeassistant.const import CONF_TYPE, CONF_NAME, CONF_ID
+from homeassistant.util.decorator import Registry
+
+from homeassistant.auth.models import Credentials
+
+_LOGGER = logging.getLogger(__name__)
+DATA_REQS = 'auth_prov_reqs_processed'
+
+AUTH_PROVIDERS = Registry()
+
+AUTH_PROVIDER_SCHEMA = vol.Schema({
+    vol.Required(CONF_TYPE): str,
+    vol.Optional(CONF_NAME): str,
+    # Specify ID if you have two auth providers for same type.
+    vol.Optional(CONF_ID): str,
+}, extra=vol.ALLOW_EXTRA)
+
+
+async def auth_provider_from_config(hass, store, config):
+    """Initialize an auth provider from a config."""
+    provider_name = config[CONF_TYPE]
+    module = await load_auth_provider_module(hass, provider_name)
+
+    if module is None:
+        return None
+
+    try:
+        config = module.CONFIG_SCHEMA(config)
+    except vol.Invalid as err:
+        _LOGGER.error('Invalid configuration for auth provider %s: %s',
+                      provider_name, humanize_error(config, err))
+        return None
+
+    return AUTH_PROVIDERS[provider_name](hass, store, config)
+
+
+async def load_auth_provider_module(hass, provider):
+    """Load an auth provider."""
+    try:
+        module = importlib.import_module(
+            'homeassistant.auth.providers.{}'.format(provider))
+    except ImportError:
+        _LOGGER.warning('Unable to find auth provider %s', provider)
+        return None
+
+    if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'):
+        return module
+
+    processed = hass.data.get(DATA_REQS)
+
+    if processed is None:
+        processed = hass.data[DATA_REQS] = set()
+    elif provider in processed:
+        return module
+
+    req_success = await requirements.async_process_requirements(
+        hass, 'auth provider {}'.format(provider), module.REQUIREMENTS)
+
+    if not req_success:
+        return None
+
+    processed.add(provider)
+    return module
+
+
+class AuthProvider:
+    """Provider of user authentication."""
+
+    DEFAULT_TITLE = 'Unnamed auth provider'
+
+    initialized = False
+
+    def __init__(self, hass, store, config):
+        """Initialize an auth provider."""
+        self.hass = hass
+        self.store = store
+        self.config = config
+
+    @property
+    def id(self):  # pylint: disable=invalid-name
+        """Return id of the auth provider.
+
+        Optional, can be None.
+        """
+        return self.config.get(CONF_ID)
+
+    @property
+    def type(self):
+        """Return type of the provider."""
+        return self.config[CONF_TYPE]
+
+    @property
+    def name(self):
+        """Return the name of the auth provider."""
+        return self.config.get(CONF_NAME, self.DEFAULT_TITLE)
+
+    async def async_credentials(self):
+        """Return all credentials of this provider."""
+        users = await self.store.async_get_users()
+        return [
+            credentials
+            for user in users
+            for credentials in user.credentials
+            if (credentials.auth_provider_type == self.type and
+                credentials.auth_provider_id == self.id)
+        ]
+
+    @callback
+    def async_create_credentials(self, data):
+        """Create credentials."""
+        return Credentials(
+            auth_provider_type=self.type,
+            auth_provider_id=self.id,
+            data=data,
+        )
+
+    # Implement by extending class
+
+    async def async_initialize(self):
+        """Initialize the auth provider.
+
+        Optional.
+        """
+
+    async def async_credential_flow(self):
+        """Return the data flow for logging in with auth provider."""
+        raise NotImplementedError
+
+    async def async_get_or_create_credentials(self, flow_result):
+        """Get credentials based on the flow result."""
+        raise NotImplementedError
+
+    async def async_user_meta_for_credentials(self, credentials):
+        """Return extra user metadata for credentials.
+
+        Will be used to populate info when creating a new user.
+        """
+        return {}
diff --git a/homeassistant/auth_providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py
similarity index 93%
rename from homeassistant/auth_providers/homeassistant.py
rename to homeassistant/auth/providers/homeassistant.py
index c4d2021f6ce054ccf7b25b21216db1ac21c1fb26..fa6878da065743bf8188729713246348d6a1336b 100644
--- a/homeassistant/auth_providers/homeassistant.py
+++ b/homeassistant/auth/providers/homeassistant.py
@@ -6,14 +6,17 @@ import hmac
 
 import voluptuous as vol
 
-from homeassistant import auth, data_entry_flow
+from homeassistant import data_entry_flow
 from homeassistant.exceptions import HomeAssistantError
 
+from homeassistant.auth.util import generate_secret
+
+from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS
 
 STORAGE_VERSION = 1
 STORAGE_KEY = 'auth_provider.homeassistant'
 
-CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
+CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
 }, extra=vol.PREVENT_EXTRA)
 
 
@@ -43,7 +46,7 @@ class Data:
 
         if data is None:
             data = {
-                'salt': auth.generate_secret(),
+                'salt': generate_secret(),
                 'users': []
             }
 
@@ -112,8 +115,8 @@ class Data:
         await self._store.async_save(self._data)
 
 
-@auth.AUTH_PROVIDERS.register('homeassistant')
-class HassAuthProvider(auth.AuthProvider):
+@AUTH_PROVIDERS.register('homeassistant')
+class HassAuthProvider(AuthProvider):
     """Auth provider based on a local storage of users in HASS config dir."""
 
     DEFAULT_TITLE = 'Home Assistant Local'
diff --git a/homeassistant/auth_providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py
similarity index 93%
rename from homeassistant/auth_providers/insecure_example.py
rename to homeassistant/auth/providers/insecure_example.py
index a8e8cd0cb0e12156daaf602522efde2f603d034c..e06b16177a1241632df1fb5d91cebc4616e59654 100644
--- a/homeassistant/auth_providers/insecure_example.py
+++ b/homeassistant/auth/providers/insecure_example.py
@@ -5,9 +5,11 @@ import hmac
 import voluptuous as vol
 
 from homeassistant.exceptions import HomeAssistantError
-from homeassistant import auth, data_entry_flow
+from homeassistant import data_entry_flow
 from homeassistant.core import callback
 
+from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS
+
 
 USER_SCHEMA = vol.Schema({
     vol.Required('username'): str,
@@ -16,7 +18,7 @@ USER_SCHEMA = vol.Schema({
 })
 
 
-CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
+CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
     vol.Required('users'): [USER_SCHEMA]
 }, extra=vol.PREVENT_EXTRA)
 
@@ -25,8 +27,8 @@ class InvalidAuthError(HomeAssistantError):
     """Raised when submitting invalid authentication."""
 
 
-@auth.AUTH_PROVIDERS.register('insecure_example')
-class ExampleAuthProvider(auth.AuthProvider):
+@AUTH_PROVIDERS.register('insecure_example')
+class ExampleAuthProvider(AuthProvider):
     """Example auth provider based on hardcoded usernames and passwords."""
 
     async def async_credential_flow(self):
diff --git a/homeassistant/auth_providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py
similarity index 91%
rename from homeassistant/auth_providers/legacy_api_password.py
rename to homeassistant/auth/providers/legacy_api_password.py
index 510cc4d02792fe8e52ec3b0b96daacc6f9cb1608..57c05e3bdc86bc7d079a6adc2d1047a535279b3f 100644
--- a/homeassistant/auth_providers/legacy_api_password.py
+++ b/homeassistant/auth/providers/legacy_api_password.py
@@ -9,15 +9,18 @@ import hmac
 import voluptuous as vol
 
 from homeassistant.exceptions import HomeAssistantError
-from homeassistant import auth, data_entry_flow
+from homeassistant import data_entry_flow
 from homeassistant.core import callback
 
+from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS
+
+
 USER_SCHEMA = vol.Schema({
     vol.Required('username'): str,
 })
 
 
-CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
+CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({
 }, extra=vol.PREVENT_EXTRA)
 
 LEGACY_USER = 'homeassistant'
@@ -27,8 +30,8 @@ class InvalidAuthError(HomeAssistantError):
     """Raised when submitting invalid authentication."""
 
 
-@auth.AUTH_PROVIDERS.register('legacy_api_password')
-class LegacyApiPasswordAuthProvider(auth.AuthProvider):
+@AUTH_PROVIDERS.register('legacy_api_password')
+class LegacyApiPasswordAuthProvider(AuthProvider):
     """Example auth provider based on hardcoded usernames and passwords."""
 
     DEFAULT_TITLE = 'Legacy API Password'
diff --git a/homeassistant/auth/util.py b/homeassistant/auth/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..402caae4618d0f00894a1cefa6ba914e5faf104e
--- /dev/null
+++ b/homeassistant/auth/util.py
@@ -0,0 +1,13 @@
+"""Auth utils."""
+import binascii
+import os
+
+
+def generate_secret(entropy: int = 32) -> str:
+    """Generate a secret.
+
+    Backport of secrets.token_hex from Python 3.6
+
+    Event loop friendly.
+    """
+    return binascii.hexlify(os.urandom(entropy)).decode('ascii')
diff --git a/homeassistant/auth_providers/__init__.py b/homeassistant/auth_providers/__init__.py
deleted file mode 100644
index 4705e7580ca4471169a0cd8754c7a660030b1691..0000000000000000000000000000000000000000
--- a/homeassistant/auth_providers/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Auth providers for Home Assistant."""
diff --git a/homeassistant/components/rachio.py b/homeassistant/components/rachio.py
index b3b2d05e9334858587d6b563939898a1a6fbfddc..3a804c50c74a5ac85bbd10c3205397a12e016a01 100644
--- a/homeassistant/components/rachio.py
+++ b/homeassistant/components/rachio.py
@@ -10,7 +10,7 @@ import logging
 from aiohttp import web
 import voluptuous as vol
 
-from homeassistant.auth import generate_secret
+from homeassistant.auth.util import generate_secret
 from homeassistant.components.http import HomeAssistantView
 from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API
 import homeassistant.helpers.config_validation as cv
diff --git a/homeassistant/config.py b/homeassistant/config.py
index 52ff0e19c598b25693bb41b58a2aff7ab2073513..48632ccab837239391b01c61f0b3d2708b1f43ff 100644
--- a/homeassistant/config.py
+++ b/homeassistant/config.py
@@ -13,6 +13,7 @@ import voluptuous as vol
 from voluptuous.humanize import humanize_error
 
 from homeassistant import auth
+from homeassistant.auth import providers as auth_providers
 from homeassistant.const import (
     ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ASSUMED_STATE,
     CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, CONF_UNIT_SYSTEM,
@@ -159,7 +160,7 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend({
         vol.All(cv.ensure_list, [vol.IsDir()]),
     vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA,
     vol.Optional(CONF_AUTH_PROVIDERS):
-        vol.All(cv.ensure_list, [auth.AUTH_PROVIDER_SCHEMA])
+        vol.All(cv.ensure_list, [auth_providers.AUTH_PROVIDER_SCHEMA])
 })
 
 
diff --git a/homeassistant/scripts/auth.py b/homeassistant/scripts/auth.py
index dacdc7b18e24ede6cc6d0907cce356938a9ba42e..aa39e9f66df278a587ab498c9cd9dc53cfd67b51 100644
--- a/homeassistant/scripts/auth.py
+++ b/homeassistant/scripts/auth.py
@@ -5,7 +5,7 @@ import os
 
 from homeassistant.core import HomeAssistant
 from homeassistant.config import get_default_config_dir
-from homeassistant.auth_providers import homeassistant as hass_auth
+from homeassistant.auth.providers import homeassistant as hass_auth
 
 
 def run(args):
diff --git a/tests/auth/__init__.py b/tests/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..48a99324b304e2e36b8e5e6a151fb0fa50c8eafd
--- /dev/null
+++ b/tests/auth/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Home Assistant auth module."""
diff --git a/tests/auth_providers/__init__.py b/tests/auth/providers/__init__.py
similarity index 100%
rename from tests/auth_providers/__init__.py
rename to tests/auth/providers/__init__.py
diff --git a/tests/auth_providers/test_homeassistant.py b/tests/auth/providers/test_homeassistant.py
similarity index 97%
rename from tests/auth_providers/test_homeassistant.py
rename to tests/auth/providers/test_homeassistant.py
index 1d9a29bf48b7be0d53287affa9003e290b0bccb0..98701ba2857e600eefd169f565db6a286b8c31d5 100644
--- a/tests/auth_providers/test_homeassistant.py
+++ b/tests/auth/providers/test_homeassistant.py
@@ -2,7 +2,7 @@
 import pytest
 
 from homeassistant import data_entry_flow
-from homeassistant.auth_providers import homeassistant as hass_auth
+from homeassistant.auth.providers import homeassistant as hass_auth
 
 
 @pytest.fixture
diff --git a/tests/auth_providers/test_insecure_example.py b/tests/auth/providers/test_insecure_example.py
similarity index 91%
rename from tests/auth_providers/test_insecure_example.py
rename to tests/auth/providers/test_insecure_example.py
index cb0bab4afed04f0eb419f26a82959a9604c91dc2..8e8c973875656778374d82273a8b881284e3b941 100644
--- a/tests/auth_providers/test_insecure_example.py
+++ b/tests/auth/providers/test_insecure_example.py
@@ -4,8 +4,8 @@ import uuid
 
 import pytest
 
-from homeassistant import auth
-from homeassistant.auth_providers import insecure_example
+from homeassistant.auth import auth_store, models as auth_models
+from homeassistant.auth.providers import insecure_example
 
 from tests.common import mock_coro
 
@@ -13,7 +13,7 @@ from tests.common import mock_coro
 @pytest.fixture
 def store(hass):
     """Mock store."""
-    return auth.AuthStore(hass)
+    return auth_store.AuthStore(hass)
 
 
 @pytest.fixture
@@ -45,7 +45,7 @@ async def test_create_new_credential(provider):
 
 async def test_match_existing_credentials(store, provider):
     """See if we match existing users."""
-    existing = auth.Credentials(
+    existing = auth_models.Credentials(
         id=uuid.uuid4(),
         auth_provider_type='insecure_example',
         auth_provider_id=None,
diff --git a/tests/auth_providers/test_legacy_api_password.py b/tests/auth/providers/test_legacy_api_password.py
similarity index 94%
rename from tests/auth_providers/test_legacy_api_password.py
rename to tests/auth/providers/test_legacy_api_password.py
index 3a186a0454c6db8996cbff96daf559630c58fe75..007e37b90c4e3eca95176c5f909b979912b87db5 100644
--- a/tests/auth_providers/test_legacy_api_password.py
+++ b/tests/auth/providers/test_legacy_api_password.py
@@ -4,13 +4,14 @@ from unittest.mock import Mock
 import pytest
 
 from homeassistant import auth
-from homeassistant.auth_providers import legacy_api_password
+from homeassistant.auth import auth_store
+from homeassistant.auth.providers import legacy_api_password
 
 
 @pytest.fixture
 def store(hass):
     """Mock store."""
-    return auth.AuthStore(hass)
+    return auth_store.AuthStore(hass)
 
 
 @pytest.fixture
diff --git a/tests/test_auth.py b/tests/auth/test_init.py
similarity index 91%
rename from tests/test_auth.py
rename to tests/auth/test_init.py
index a53c5aaec99b8cc71aacee12279319173f5187ea..805369a6da87210b998c3124d8b782a01c993056 100644
--- a/tests/test_auth.py
+++ b/tests/auth/test_init.py
@@ -5,6 +5,8 @@ from unittest.mock import Mock, patch
 import pytest
 
 from homeassistant import auth, data_entry_flow
+from homeassistant.auth import (
+    models as auth_models, auth_store, const as auth_const)
 from homeassistant.util import dt as dt_util
 from tests.common import (
     MockUser, ensure_auth_manager_loaded, flush_store, CLIENT_ID)
@@ -101,7 +103,7 @@ async def test_login_as_existing_user(mock_hass):
         is_active=False,
         name='Not user',
     ).add_to_auth_manager(manager)
-    user.credentials.append(auth.Credentials(
+    user.credentials.append(auth_models.Credentials(
         id='mock-id2',
         auth_provider_type='insecure_example',
         auth_provider_id=None,
@@ -116,7 +118,7 @@ async def test_login_as_existing_user(mock_hass):
         is_active=False,
         name='Paulus',
     ).add_to_auth_manager(manager)
-    user.credentials.append(auth.Credentials(
+    user.credentials.append(auth_models.Credentials(
         id='mock-id',
         auth_provider_type='insecure_example',
         auth_provider_id=None,
@@ -203,7 +205,7 @@ async def test_saving_loading(hass, hass_storage):
 
     await flush_store(manager._store._store)
 
-    store2 = auth.AuthStore(hass)
+    store2 = auth_store.AuthStore(hass)
     users = await store2.async_get_users()
     assert len(users) == 1
     assert users[0] == user
@@ -211,23 +213,25 @@ async def test_saving_loading(hass, hass_storage):
 
 def test_access_token_expired():
     """Test that the expired property on access tokens work."""
-    refresh_token = auth.RefreshToken(
+    refresh_token = auth_models.RefreshToken(
         user=None,
         client_id='bla'
     )
 
-    access_token = auth.AccessToken(
+    access_token = auth_models.AccessToken(
         refresh_token=refresh_token
     )
 
     assert access_token.expired is False
 
-    with patch('homeassistant.auth.dt_util.utcnow',
-               return_value=dt_util.utcnow() + auth.ACCESS_TOKEN_EXPIRATION):
+    with patch('homeassistant.util.dt.utcnow',
+               return_value=dt_util.utcnow() +
+               auth_const.ACCESS_TOKEN_EXPIRATION):
         assert access_token.expired is True
 
-    almost_exp = dt_util.utcnow() + auth.ACCESS_TOKEN_EXPIRATION - timedelta(1)
-    with patch('homeassistant.auth.dt_util.utcnow', return_value=almost_exp):
+    almost_exp = \
+        dt_util.utcnow() + auth_const.ACCESS_TOKEN_EXPIRATION - timedelta(1)
+    with patch('homeassistant.util.dt.utcnow', return_value=almost_exp):
         assert access_token.expired is False
 
 
@@ -242,8 +246,9 @@ async def test_cannot_retrieve_expired_access_token(hass):
     access_token = manager.async_create_access_token(refresh_token)
     assert manager.async_get_access_token(access_token.token) is access_token
 
-    with patch('homeassistant.auth.dt_util.utcnow',
-               return_value=dt_util.utcnow() + auth.ACCESS_TOKEN_EXPIRATION):
+    with patch('homeassistant.util.dt.utcnow',
+               return_value=dt_util.utcnow() +
+               auth_const.ACCESS_TOKEN_EXPIRATION):
         assert manager.async_get_access_token(access_token.token) is None
 
     # Even with unpatched time, it should have been removed from manager
diff --git a/tests/common.py b/tests/common.py
index 98a3b0a6074e2b6d991b0da0de7c42bb536d40e3..b3da5e0d0987ba06bab65f9cf52d1398e0be0fe7 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -12,6 +12,7 @@ import threading
 from contextlib import contextmanager
 
 from homeassistant import auth, core as ha, data_entry_flow, config_entries
+from homeassistant.auth import models as auth_models, auth_store
 from homeassistant.setup import setup_component, async_setup_component
 from homeassistant.config import async_process_component_config
 from homeassistant.helpers import (
@@ -114,7 +115,7 @@ def async_test_home_assistant(loop):
     """Return a Home Assistant object pointing at test config dir."""
     hass = ha.HomeAssistant(loop)
     hass.config.async_load = Mock()
-    store = auth.AuthStore(hass)
+    store = auth_store.AuthStore(hass)
     hass.auth = auth.AuthManager(hass, store, {})
     ensure_auth_manager_loaded(hass.auth)
     INSTANCES.append(hass)
@@ -308,7 +309,7 @@ def mock_registry(hass, mock_entries=None):
     return registry
 
 
-class MockUser(auth.User):
+class MockUser(auth_models.User):
     """Mock a user in Home Assistant."""
 
     def __init__(self, id='mock-id', is_owner=True, is_active=True,
diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py
index 3e5eed4c924b55a1399cbf02161f00cc08b19837..197859584228363c5e84671c9c92b53a6ee1bc45 100644
--- a/tests/components/http/test_auth.py
+++ b/tests/components/http/test_auth.py
@@ -7,7 +7,7 @@ import pytest
 from aiohttp import BasicAuth, web
 from aiohttp.web_exceptions import HTTPUnauthorized
 
-from homeassistant.auth import AccessToken, RefreshToken
+from homeassistant.auth.models import AccessToken, RefreshToken
 from homeassistant.components.http.auth import setup_auth
 from homeassistant.components.http.const import KEY_AUTHENTICATED
 from homeassistant.components.http.real_ip import setup_real_ip
diff --git a/tests/scripts/test_auth.py b/tests/scripts/test_auth.py
index e6aa7893f33b0aad6e3cc624d97718da08028c6f..cd0524eb032660daf93a88ec269dd1420cdf52a4 100644
--- a/tests/scripts/test_auth.py
+++ b/tests/scripts/test_auth.py
@@ -4,7 +4,7 @@ from unittest.mock import Mock, patch
 import pytest
 
 from homeassistant.scripts import auth as script_auth
-from homeassistant.auth_providers import homeassistant as hass_auth
+from homeassistant.auth.providers import homeassistant as hass_auth
 
 
 @pytest.fixture