diff --git a/.gitignore b/.gitignore
index 8af43e7..5adbcf0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,4 +55,5 @@ docs/_build/
 # PyBuilder
 target/
 
-coverage
\ No newline at end of file
+coverage
+.pytest_cache
\ No newline at end of file
diff --git a/CHANGES.txt b/CHANGES.txt
index 42ac4f0..6c7d59a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,10 +1,23 @@
 Changes
 =======
 
+0.3.0 (2018-09-06)
+------------------
+
+- Deprecate ``login_required`` and ``has_permission`` decorators.
+  Use ``check_authorized`` and ``check_permission`` helper functions instead.
+
+- Bump supported ``aiohttp`` version to 3.0+
+
+- Enable strong warnings mode for test suite, clean-up all deprecation
+  warnings.
+
+- Polish documentation
+
 0.2.0 (2017-11-17)
 ------------------
 
-- Add `is_anonymous`, `login_required`, `has_permission` helpers (#114)
+- Add ``is_anonymous``, ``login_required``, ``has_permission`` helpers (#114)
 
 0.1.2 (2017-10-17)
 ------------------
diff --git a/aiohttp_security/__init__.py b/aiohttp_security/__init__.py
index 937d858..e9d5297 100644
--- a/aiohttp_security/__init__.py
+++ b/aiohttp_security/__init__.py
@@ -1,11 +1,12 @@
 from .abc import AbstractAuthorizationPolicy, AbstractIdentityPolicy
-from .api import (authorized_userid, forget, has_permission, is_anonymous,
-                  login_required, permits, remember, setup)
+from .api import (authorized_userid, forget, has_permission,
+                  is_anonymous, login_required, permits, remember,
+                  setup, check_authorized, check_permission)
 from .cookies_identity import CookiesIdentityPolicy
 from .session_identity import SessionIdentityPolicy
 from .jwt_identity import JWTIdentityPolicy
 
-__version__ = '0.2.0'
+__version__ = '0.3.0'
 
 
 __all__ = ('AbstractIdentityPolicy', 'AbstractAuthorizationPolicy',
@@ -13,4 +14,5 @@ __all__ = ('AbstractIdentityPolicy', 'AbstractAuthorizationPolicy',
            'JWTIdentityPolicy',
            'remember', 'forget', 'authorized_userid',
            'permits', 'setup', 'is_anonymous',
-           'login_required', 'has_permission')
+           'login_required', 'has_permission',
+           'check_authorized', 'check_permission')
diff --git a/aiohttp_security/api.py b/aiohttp_security/api.py
index d414459..d92a73e 100644
--- a/aiohttp_security/api.py
+++ b/aiohttp_security/api.py
@@ -1,4 +1,5 @@
 import enum
+import warnings
 from aiohttp import web
 from aiohttp_security.abc import (AbstractIdentityPolicy,
                                   AbstractAuthorizationPolicy)
@@ -86,6 +87,15 @@ async def is_anonymous(request):
     return False
 
 
+async def check_authorized(request):
+    """Checker that raises HTTPUnauthorized for anonymous users.
+    """
+    userid = await authorized_userid(request)
+    if userid is None:
+        raise web.HTTPUnauthorized()
+    return userid
+
+
 def login_required(fn):
     """Decorator that restrict access only for authorized users.
 
@@ -101,21 +111,34 @@ def login_required(fn):
                    "or `def handler(self, request)`.")
             raise RuntimeError(msg)
 
-        userid = await authorized_userid(request)
-        if userid is None:
-            raise web.HTTPUnauthorized
-
-        ret = await fn(*args, **kwargs)
-        return ret
+        await check_authorized(request)
+        return await fn(*args, **kwargs)
 
+    warnings.warn("login_required decorator is deprecated, "
+                  "use check_authorized instead",
+                  DeprecationWarning)
     return wrapped
 
 
+async def check_permission(request, permission, context=None):
+    """Checker that passes only to authoraised users with given permission.
+
+    If user is not authorized - raises HTTPUnauthorized,
+    if user is authorized and does not have permission -
+    raises HTTPForbidden.
+    """
+
+    await check_authorized(request)
+    allowed = await permits(request, permission, context)
+    if not allowed:
+        raise web.HTTPForbidden()
+
+
 def has_permission(
     permission,
     context=None,
 ):
-    """Decorator that restrict access only for authorized users
+    """Decorator that restricts access only for authorized users
     with correct permissions.
 
     If user is not authorized - raises HTTPUnauthorized,
@@ -132,18 +155,14 @@ def has_permission(
                        "or `def handler(self, request)`.")
                 raise RuntimeError(msg)
 
-            userid = await authorized_userid(request)
-            if userid is None:
-                raise web.HTTPUnauthorized
-
-            allowed = await permits(request, permission, context)
-            if not allowed:
-                raise web.HTTPForbidden
-            ret = await fn(*args, **kwargs)
-            return ret
+            await check_permission(request, permission, context)
+            return await fn(*args, **kwargs)
 
         return wrapped
 
+    warnings.warn("has_permission decorator is deprecated, "
+                  "use check_permission instead",
+                  DeprecationWarning)
     return wrapper
 
 
diff --git a/aiohttp_security/jwt_identity.py b/aiohttp_security/jwt_identity.py
index 1df01df..bd6ffe7 100644
--- a/aiohttp_security/jwt_identity.py
+++ b/aiohttp_security/jwt_identity.py
@@ -35,7 +35,7 @@ class JWTIdentityPolicy(AbstractIdentityPolicy):
 
         identity = jwt.decode(token,
                               self.secret,
-                              algorithm=self.algorithm)
+                              algorithms=[self.algorithm])
         return identity
 
     async def remember(self, *args, **kwargs):  # pragma: no cover
diff --git a/demo/database_auth/handlers.py b/demo/database_auth/handlers.py
index 3d10332..0cc9484 100644
--- a/demo/database_auth/handlers.py
+++ b/demo/database_auth/handlers.py
@@ -4,7 +4,7 @@ from aiohttp import web
 
 from aiohttp_security import (
     remember, forget, authorized_userid,
-    has_permission, login_required,
+    check_permission, check_authorized,
 )
 
 from .db_auth import check_credentials
@@ -45,25 +45,25 @@ class Web(object):
         db_engine = request.app.db_engine
         if await check_credentials(db_engine, login, password):
             await remember(request, response, login)
-            return response
+            raise response
 
-        return web.HTTPUnauthorized(
+        raise web.HTTPUnauthorized(
             body=b'Invalid username/password combination')
 
-    @login_required
     async def logout(self, request):
+        await check_authorized(request)
         response = web.Response(body=b'You have been logged out')
         await forget(request, response)
         return response
 
-    @has_permission('public')
     async def internal_page(self, request):
+        await check_permission(request, 'public')
         response = web.Response(
             body=b'This page is visible for all registered users')
         return response
 
-    @has_permission('protected')
     async def protected_page(self, request):
+        await check_permission(request, 'protected')
         response = web.Response(body=b'You are on protected page')
         return response
 
diff --git a/demo/database_auth/main.py b/demo/database_auth/main.py
index 43da500..822df44 100644
--- a/demo/database_auth/main.py
+++ b/demo/database_auth/main.py
@@ -19,7 +19,7 @@ async def init(loop):
                                     password='aiohttp_security',
                                     database='aiohttp_security',
                                     host='127.0.0.1')
-    app = web.Application(loop=loop)
+    app = web.Application()
     app.db_engine = db_engine
     setup_session(app, RedisStorage(redis_pool))
     setup_security(app,
diff --git a/demo/dictionary_auth/handlers.py b/demo/dictionary_auth/handlers.py
index 5816e8d..6c19bab 100644
--- a/demo/dictionary_auth/handlers.py
+++ b/demo/dictionary_auth/handlers.py
@@ -4,7 +4,7 @@ from aiohttp import web
 
 from aiohttp_security import (
     remember, forget, authorized_userid,
-    has_permission, login_required,
+    check_permission, check_authorized,
 )
 
 from .authz import check_credentials
@@ -55,8 +55,8 @@ async def login(request):
     return web.HTTPUnauthorized(body='Invalid username / password combination')
 
 
-@login_required
 async def logout(request):
+    await check_authorized(request)
     response = web.Response(
         text='You have been logged out',
         content_type='text/html',
@@ -65,9 +65,8 @@ async def logout(request):
     return response
 
 
-@has_permission('public')
 async def internal_page(request):
-    # pylint: disable=unused-argument
+    await check_permission(request, 'public')
     response = web.Response(
         text='This page is visible for all registered users',
         content_type='text/html',
@@ -75,9 +74,8 @@ async def internal_page(request):
     return response
 
 
-@has_permission('protected')
 async def protected_page(request):
-    # pylint: disable=unused-argument
+    await check_permission(request, 'protected')
     response = web.Response(
         text='You are on protected page',
         content_type='text/html',
diff --git a/demo/simple_example_auth.py b/demo/simple_example_auth.py
index d1da45f..5b4b82d 100644
--- a/demo/simple_example_auth.py
+++ b/demo/simple_example_auth.py
@@ -1,6 +1,6 @@
 from aiohttp import web
 from aiohttp_session import SimpleCookieStorage, session_middleware
-from aiohttp_security import has_permission, \
+from aiohttp_security import check_permission, \
     is_anonymous, remember, forget, \
     setup as setup_security, SessionIdentityPolicy
 from aiohttp_security.abc import AbstractAuthorizationPolicy
@@ -54,13 +54,13 @@ async def handler_logout(request):
     raise redirect_response
 
 
-@has_permission('listen')
 async def handler_listen(request):
+    await check_permission(request, 'listen')
     return web.Response(body="I can listen!")
 
 
-@has_permission('speak')
 async def handler_speak(request):
+    await check_permission(request, 'speak')
     return web.Response(body="I can speak!")
 
 
diff --git a/docs/example.rst b/docs/example.rst
index 39c839b..945d386 100644
--- a/docs/example.rst
+++ b/docs/example.rst
@@ -9,7 +9,7 @@ Simple example::
 
     from aiohttp import web
     from aiohttp_session import SimpleCookieStorage, session_middleware
-    from aiohttp_security import has_permission, \
+    from aiohttp_security import check_permission, \
         is_anonymous, remember, forget, \
         setup as setup_security, SessionIdentityPolicy
     from aiohttp_security.abc import AbstractAuthorizationPolicy
@@ -63,13 +63,13 @@ Simple example::
         raise redirect_response
 
 
-    @has_permission('listen')
     async def handler_listen(request):
+        await check_permission(request, 'listen')
         return web.Response(body="I can listen!")
 
 
-    @has_permission('speak')
     async def handler_speak(request):
+        await check_permission(request, 'speak')
         return web.Response(body="I can speak!")
 
 
@@ -85,11 +85,12 @@ Simple example::
         app = web.Application(middlewares=[middleware])
 
         # add the routes
-        app.router.add_route('GET', '/', handler_root)
-        app.router.add_route('GET', '/login', handler_login_jack)
-        app.router.add_route('GET', '/logout', handler_logout)
-        app.router.add_route('GET', '/listen', handler_listen)
-        app.router.add_route('GET', '/speak', handler_speak)
+        app.add_routes([
+            web.get('/', handler_root),
+            web.get('/login', handler_login_jack),
+            web.get('/logout', handler_logout),
+            web.get('/listen', handler_listen),
+            web.get('/speak', handler_speak)])
 
         # set up policies
         policy = SessionIdentityPolicy()
diff --git a/docs/example_db_auth.rst b/docs/example_db_auth.rst
index 07d44ef..5e9463a 100644
--- a/docs/example_db_auth.rst
+++ b/docs/example_db_auth.rst
@@ -133,7 +133,7 @@ Once we have all the code in place we can install it for our application::
                                        password='aiohttp_security',
                                        database='aiohttp_security',
                                        host='127.0.0.1')
-        app = web.Application(loop=loop)
+        app = web.Application()
         setup_session(app, RedisStorage(redis_pool))
         setup_security(app,
                        SessionIdentityPolicy(),
@@ -142,23 +142,23 @@ Once we have all the code in place we can install it for our application::
 
 
 Now we have authorization and can decorate every other view with access rights
-based on permissions. There are already implemented two decorators::
+based on permissions. There are already implemented two helpers::
 
-    from aiohttp_security import has_permission, login_required
+    from aiohttp_security import check_authorized, check_permission
 
 For each view you need to protect - just apply the decorator on it::
 
     class Web:
-        @has_permission('protected')
         async def protected_page(self, request):
+            await check_permission(request, 'protected')
             response = web.Response(body=b'You are on protected page')
             return response
 
 or::
 
     class Web:
-        @login_required
         async def logout(self, request):
+            await check_authorized(request)
             response = web.Response(body=b'You have been logged out')
             await forget(request, response)
             return response
diff --git a/docs/index.rst b/docs/index.rst
index f2ff487..1759104 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -3,6 +3,7 @@ aiohttp_security
 
 The library provides security for :ref:`aiohttp.web<aiohttp-web>`.
 
+The current version is |version|
 
 Contents
 --------
diff --git a/docs/reference.rst b/docs/reference.rst
index 788e372..f3297e0 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -13,6 +13,19 @@
 Public API functions
 ====================
 
+.. function:: setup(app, identity_policy, autz_policy)
+
+   Setup :mod:`aiohttp` application with security policies.
+
+   :param app: aiohttp :class:`aiohttp.web.Application` instance.
+
+   :param identity_policy: indentification policy, an
+                           :class:`AbstractIdentityPolicy` instance.
+
+   :param autz_policy: authorization policy, an
+                           :class:`AbstractAuthorizationPolicy` instance.
+
+
 .. coroutinefunction:: remember(request, response, identity, **kwargs)
 
    Remember *identity* in *response*, e.g. by storing a cookie or
@@ -50,6 +63,41 @@ Public API functions
                     descendants like :class:`aiohttp.web.Response`.
 
 
+.. coroutinefunction:: check_authorized(request)
+
+   Checker that doesn't pass if user is not authorized by *request*.
+
+   :param request:  :class:`aiohttp.web.Request` object.
+
+   :return str: authorized user ID if success
+
+   :raise: :class:`aiohttp.web.HTTPUnauthorized` for anonymous users.
+
+   Usage::
+
+      async def handler(request):
+          await check_authorized(request)
+          # this line is never executed for anonymous users
+
+
+.. coroutinefunction:: check_permission(request, permission)
+
+   Checker that doesn't pass if user has no requested permission.
+
+   :param request:  :class:`aiohttp.web.Request` object.
+
+   :raise: :class:`aiohttp.web.HTTPUnauthorized` for anonymous users.
+
+   :raise: :class:`aiohttp.web.HTTPForbidden` if user is
+           authorized but has no access rights.
+
+   Usage::
+
+      async def handler(request):
+          await check_permission(request, 'read')
+          # this line is never executed if a user has no read permission
+
+
 .. coroutinefunction:: authorized_userid(request)
 
    Retrieve :term:`userid`.
@@ -78,7 +126,8 @@ Public API functions
 
    :param request: :class:`aiohttp.web.Request` object.
 
-   :param permission: Requested :term:`permission`. :class:`str` or :class:`enum.Enum` object.
+   :param permission: Requested :term:`permission`. :class:`str` or
+                      :class:`enum.Enum` object.
 
    :param context: additional object may be passed into
                    :meth:`AbstractAuthorizationPolicy.permission`
@@ -92,7 +141,8 @@ Public API functions
 
    Checks if user is anonymous user.
 
-   Return ``True`` if user is not remembered in request, otherwise returns ``False``.
+   Return ``True`` if user is not remembered in request, otherwise
+   returns ``False``.
 
    :param request: :class:`aiohttp.web.Request` object.
 
@@ -103,29 +153,27 @@ Public API functions
 
    Raises :class:`aiohttp.web.HTTPUnauthorized` if user is not authorized.
 
+   .. deprecated:: 0.3
+
+      Use :func:`check_authorized` async function.
+
 
 .. decorator:: has_permission(permission)
 
    Decorator for handlers that checks if user is authorized
    and has correct permission.
 
-   Raises :class:`aiohttp.web.HTTPUnauthorized` if user is not authorized.
-   Raises :class:`aiohttp.web.HTTPForbidden` if user is authorized but has no access rights.
+   Raises :class:`aiohttp.web.HTTPUnauthorized` if user is not
+   authorized.
+
+   Raises :class:`aiohttp.web.HTTPForbidden` if user is
+   authorized but has no access rights.
 
    :param str permission: requested :term:`permission`.
 
+   .. deprecated:: 0.3
 
-.. function:: setup(app, identity_policy, autz_policy)
-
-   Setup :mod:`aiohttp` application with security policies.
-
-   :param app: aiohttp :class:`aiohttp.web.Application` instance.
-
-   :param identity_policy: indentification policy, an
-                           :class:`AbstractIdentityPolicy` instance.
-
-   :param autz_policy: authorization policy, an
-                           :class:`AbstractAuthorizationPolicy` instance.
+      Use :func:`check_authorized` async function.
 
 
 Abstract policies
diff --git a/docs/usage.rst b/docs/usage.rst
index 9fe28c5..a772a4b 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -11,22 +11,30 @@
 
 First of all, what is *aiohttp_security* about?
 
-*aiohttp_security* is a set of public API functions as well as a reference standard for implementation details for securing access to assets served by a wsgi server.
-Assets are secured using authentication and authorization as explained below.  *aiohttp_security* is part of the *aio_libs* project which takes advantage of asynchronous
-processing using Python's asyncio library.
+*aiohttp-security* is a set of public API functions as well as a
+reference standard for implementation details for securing access to
+assets served by a wsgi server.
+
+Assets are secured using authentication and authorization as explained
+below.  *aiohttp-security* is part of the
+`aio-libs <https://github.com/aio-libs>`_ project which takes advantage
+of asynchronous processing using Python's asyncio library.
 
 
 Public API
 ==========
 
-The API is agnostic to the low level implementation details such that all client code only needs to implement the endpoints as provided by the API (instead of calling policy
-code directly (see explanation below)).
+The API is agnostic to the low level implementation details such that
+all client code only needs to implement the endpoints as provided by
+the API (instead of calling policy code directly (see explanation
+below)).
 
 Via the API an application can:
 
 (i) remember a user in a local session (:func:`remember`),
 (ii) forget a user in a local session (:func:`forget`),
-(iii) retrieve the :term:`userid` (:func:`authorized_userid`) of a remembered user from an :term:`identity` (discussed below), and
+(iii) retrieve the :term:`userid` (:func:`authorized_userid`) of a
+      remembered user from an :term:`identity` (discussed below), and
 (iv) check the :term:`permission` of a remembered user (:func:`permits`).
 
 The library internals are built on top of two concepts:
@@ -34,52 +42,100 @@ The library internals are built on top of two concepts:
 1) :term:`authentication`, and
 2) :term:`authorization`.
 
-There are abstract base classes for both types as well as several pre-built implementations
-that are shipped with the library. However, the end user is free to build their own implementations.
-The library comes with two pre-built identity policies; one that uses cookies, and one that uses sessions [#f1]_. 
-It is envisioned that in most use cases developers will use one of the provided identity policies (Cookie or Session) and
-implement their own authorization policy.
+There are  abstract base  classes for  both types  as well  as several
+pre-built implementations that are  shipped with the library. However,
+the end user is free to build their own implementations.
+
+The library comes with two pre-built identity policies; one that uses
+cookies, and one that uses sessions [#f1]_.  It is envisioned that in
+most use cases developers will use one of the provided identity
+policies (Cookie or Session) and implement their own authorization
+policy.
 
 The workflow is as follows:
 
 1) User is authenticated.  This has to be implemented by the developer.
-2) Once user is authenticated an identity string has to be created for that user.  This has to be implemented by the developer.
-3) The identity string is passed to the Identity Policy's remember method and the user is now remembered (Cookie or Session if using built-in).  *Only once a user is remembered can the other API methods:* :func:`permits`, :func:`forget`, *and* :func:`authorized_userid` *be invoked* .
-4) If the user tries to access a restricted asset the :func:`permits` method is called.  Usually assets are protected using the **@aiohttp_security.has_permission(**\ *permission*\ **)** decorator.  This should return True if permission is granted.  
+2) Once user is authenticated an identity string has to be created for
+   that user.  This has to be implemented by the developer.
+3) The identity string is passed to the Identity Policy's remember
+   method and the user is now remembered (Cookie or Session if using
+   built-in).  *Only once a user is remembered can the other API
+   methods:* :func:`permits`, :func:`forget`, *and*
+   :func:`authorized_userid` *be invoked* .
+4) If the user tries to access a restricted asset the :func:`permits`
+   method is called.  Usually assets are protected using the
+   :func:`check_permission` helper.  This should return True if
+   permission is granted.
 
-   The :func:`permits` method is implemented by the developer as part of the :class:`AbstractAuthorizationPolicy` and passed to the application at runtime via setup. 
- In addition a  :func:`@aiohttp_security.login_required decorator` also exists that requires no permissions (i.e. doesn't call :func:`permits` method) but only requires that the user is remembered (i.e. authenticated/logged in).
+The :func:`permits` method is implemented by the developer as part of
+the :class:`AbstractAuthorizationPolicy` and passed to the
+application at runtime via setup.
+
+In addition a :func:`check_authorized` also
+exists that requires no permissions (i.e. doesn't call :func:`permits`
+method) but only requires that the user is remembered
+(i.e. authenticated/logged in).
 
 
 
 
 Authentication
 ==============
-Authentication is the process where a user's identity is verified. It confirms who the user is. This is traditionally done using a user name and password (note: this is not the only way).
-A authenticated user has no access rights, rather an authenticated user merely confirms that the user exists and that the user is who they say they are.
 
-In *aiohttp_security* the developer is responsible for their own authentication mechanism.  *aiohttp_security* only requires that the authentication result in a identity string which
-corresponds to a user's id in the underlying system.  
+Authentication is the process where a user's identity is verified. It
+confirms who the user is. This is traditionally done using a user name
+and password (note: this is not the only way).
 
- *Note:* :term:`identity` is a string that is shared between the browser and the server.  Therefore it is recommended that a random string such as a uuid or hash is used rather than things like a database primary key, user login/email, etc.
+A authenticated user has no access rights, rather an authenticated
+user merely confirms that the user exists and that the user is who
+they say they are.
+
+In *aiohttp_security* the developer is responsible for their own
+authentication mechanism.  *aiohttp_security* only requires that the
+authentication result in a identity string which corresponds to a
+user's id in the underlying system.
+
+.. note::
+
+   :term:`identity` is a string that is shared between the browser and
+   the server.  Therefore it is recommended that a random string
+   such as a uuid or hash is used rather than things like a
+   database primary key, user login/email, etc.
 
 Identity Policy
-==============
+===============
 
-Once a user is authenticated the *aiohttp_security* API is invoked for storing, retrieving,  and removing a user's :term:`identity`.  This is accommplished via AbstractIdentityPolicy's :func:`remember`, :func:`identify`, and :func:`forget` methods.  The Identity Policy is therefore the mechanism by which a authenticated user is persisted in the system.
+Once a user is authenticated the *aiohttp_security* API is invoked for
+storing, retrieving, and removing a user's :term:`identity`.  This is
+accommplished via AbstractIdentityPolicy's :func:`remember`,
+:func:`identify`, and :func:`forget` methods.  The Identity Policy is
+therefore the mechanism by which a authenticated user is persisted in
+the system.
 
-*aiohttp_security* has two built in identity policy's for this purpose.  :term:`CookiesIdentityPolicy` that uses cookies and :term:`SessionIdentityPolicy` that uses sessions via :term:`aiohttp.session` library.
+*aiohttp_security* has two built in identity policy's for this
+purpose.  :class:`CookiesIdentityPolicy` that uses cookies and
+:class:`SessionIdentityPolicy` that uses sessions via
+``aiohttp-session`` library.
 
 Authorization
 ==============
 
-Once a user is authenticated (see above) it means that the user has an :term:`identity`.  This :term:`identity` can now be used for checking access rights or :term:`permission` using a :term:`authorization` policy.
+Once a user is authenticated (see above) it means that the user has an
+:term:`identity`.  This :term:`identity` can now be used for checking
+access rights or :term:`permission` using a :term:`authorization`
+policy.
+
 The authorization policy's :func:`permits()` method is used for this purpose.
 
 
-When :class:`aiohttp.web.Request` has an :term:`identity` it means the user has been authenticated and therefore has an :term:`identity` that can be checked by the :term:`authorization` policy.
+When :class:`aiohttp.web.Request` has an :term:`identity` it means the
+user has been authenticated and therefore has an :term:`identity` that
+can be checked by the :term:`authorization` policy.
 
- As noted above, :term:`identity` is a string that is shared between the browser and the server.  Therefore it is recommended that a random string such as a uuid or hash is used rather than things like a database primary key, user login/email, etc.
+As noted above, :term:`identity` is a string that is shared between
+the browser and the server.  Therefore it is recommended that a
+random string such as a uuid or hash is used rather than things like
+a database primary key, user login/email, etc.
 
 
 .. rubric:: Footnotes
diff --git a/requirements-dev.txt b/requirements-dev.txt
index fdf3dc3..0eef8a3 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,5 +1,6 @@
 -e .
 flake8==3.5.0
+async-timeout==3.0
 pytest==3.7.4
 pytest-cov==2.5.1
 pytest-mock==1.10.0
@@ -14,4 +15,4 @@ passlib==1.7.1
 cryptography==2.3.1
 aiohttp==3.4.2
 pytest-aiohttp==0.3.0
-pyjwt==1.6.4
\ No newline at end of file
+pyjwt==1.6.4
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..8d6446c
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[tool:pytest]
+testpaths = tests
+filterwarnings=
+    error
diff --git a/setup.py b/setup.py
index fa92657..2217951 100644
--- a/setup.py
+++ b/setup.py
@@ -27,7 +27,7 @@ def read(f):
     return open(os.path.join(os.path.dirname(__file__), f)).read().strip()
 
 
-install_requires = ['aiohttp>=0.18']
+install_requires = ['aiohttp>=3.0.0']
 tests_require = install_requires + ['pytest']
 extras_require = {'session': 'aiohttp-session'}
 
diff --git a/tests/test_cookies_identity.py b/tests/test_cookies_identity.py
index 8d739e6..a003690 100644
--- a/tests/test_cookies_identity.py
+++ b/tests/test_cookies_identity.py
@@ -15,23 +15,23 @@ class Autz(AbstractAuthorizationPolicy):
         pass
 
 
-async def test_remember(loop, test_client):
+async def test_remember(loop, aiohttp_client):
 
     async def handler(request):
         response = web.Response()
         await remember(request, response, 'Andrew')
         return response
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', handler)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.get('/')
     assert 200 == resp.status
     assert 'Andrew' == resp.cookies['AIOHTTP_SECURITY'].value
 
 
-async def test_identify(loop, test_client):
+async def test_identify(loop, aiohttp_client):
 
     async def create(request):
         response = web.Response()
@@ -44,11 +44,11 @@ async def test_identify(loop, test_client):
         assert 'Andrew' == user_id
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', check)
     app.router.add_route('POST', '/', create)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.post('/')
     assert 200 == resp.status
     await resp.release()
@@ -56,7 +56,7 @@ async def test_identify(loop, test_client):
     assert 200 == resp.status
 
 
-async def test_forget(loop, test_client):
+async def test_forget(loop, aiohttp_client):
 
     async def index(request):
         return web.Response()
@@ -64,19 +64,19 @@ async def test_forget(loop, test_client):
     async def login(request):
         response = web.HTTPFound(location='/')
         await remember(request, response, 'Andrew')
-        return response
+        raise response
 
     async def logout(request):
         response = web.HTTPFound(location='/')
         await forget(request, response)
-        return response
+        raise response
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', index)
     app.router.add_route('POST', '/login', login)
     app.router.add_route('POST', '/logout', logout)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.post('/login')
     assert 200 == resp.status
     assert str(resp.url).endswith('/')
diff --git a/tests/test_dict_autz.py b/tests/test_dict_autz.py
index 9151ce0..a828aa5 100644
--- a/tests/test_dict_autz.py
+++ b/tests/test_dict_autz.py
@@ -1,10 +1,12 @@
 import enum
+import pytest
 
 from aiohttp import web
 from aiohttp_security import setup as _setup
 from aiohttp_security import (AbstractAuthorizationPolicy, authorized_userid,
                               forget, has_permission, is_anonymous,
-                              login_required, permits, remember)
+                              login_required, permits, remember,
+                              check_authorized, check_permission)
 from aiohttp_security.cookies_identity import CookiesIdentityPolicy
 
 
@@ -23,23 +25,23 @@ class Autz(AbstractAuthorizationPolicy):
             return None
 
 
-async def test_authorized_userid(loop, test_client):
+async def test_authorized_userid(loop, aiohttp_client):
 
     async def login(request):
         response = web.HTTPFound(location='/')
         await remember(request, response, 'UserID')
-        return response
+        raise response
 
     async def check(request):
         userid = await authorized_userid(request)
         assert 'Andrew' == userid
         return web.Response(text=userid)
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', check)
     app.router.add_route('POST', '/login', login)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
 
     resp = await client.post('/login')
     assert 200 == resp.status
@@ -47,23 +49,23 @@ async def test_authorized_userid(loop, test_client):
     assert 'Andrew' == txt
 
 
-async def test_authorized_userid_not_authorized(loop, test_client):
+async def test_authorized_userid_not_authorized(loop, aiohttp_client):
 
     async def check(request):
         userid = await authorized_userid(request)
         assert userid is None
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', check)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
 
     resp = await client.get('/')
     assert 200 == resp.status
 
 
-async def test_permits_enum_permission(loop, test_client):
+async def test_permits_enum_permission(loop, aiohttp_client):
     class Permission(enum.Enum):
         READ = '101'
         WRITE = '102'
@@ -86,7 +88,7 @@ async def test_permits_enum_permission(loop, test_client):
     async def login(request):
         response = web.HTTPFound(location='/')
         await remember(request, response, 'UserID')
-        return response
+        raise response
 
     async def check(request):
         ret = await permits(request, Permission.READ)
@@ -97,16 +99,16 @@ async def test_permits_enum_permission(loop, test_client):
         assert not ret
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', check)
     app.router.add_route('POST', '/login', login)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.post('/login')
     assert 200 == resp.status
 
 
-async def test_permits_unauthorized(loop, test_client):
+async def test_permits_unauthorized(loop, aiohttp_client):
 
     async def check(request):
         ret = await permits(request, 'read')
@@ -117,38 +119,38 @@ async def test_permits_unauthorized(loop, test_client):
         assert not ret
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', check)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.get('/')
     assert 200 == resp.status
 
 
-async def test_is_anonymous(loop, test_client):
+async def test_is_anonymous(loop, aiohttp_client):
 
     async def index(request):
         is_anon = await is_anonymous(request)
         if is_anon:
-            return web.HTTPUnauthorized()
-        return web.HTTPOk()
+            raise web.HTTPUnauthorized()
+        return web.Response()
 
     async def login(request):
         response = web.HTTPFound(location='/')
         await remember(request, response, 'UserID')
-        return response
+        raise response
 
     async def logout(request):
         response = web.HTTPFound(location='/')
         await forget(request, response)
-        return response
+        raise response
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', index)
     app.router.add_route('POST', '/login', login)
     app.router.add_route('POST', '/logout', logout)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.get('/')
     assert web.HTTPUnauthorized.status_code == resp.status
 
@@ -161,27 +163,63 @@ async def test_is_anonymous(loop, test_client):
     assert web.HTTPUnauthorized.status_code == resp.status
 
 
-async def test_login_required(loop, test_client):
-    @login_required
+async def test_login_required(loop, aiohttp_client):
+    with pytest.raises(DeprecationWarning):
+
+        @login_required
+        async def index(request):
+            return web.Response()
+
+        async def login(request):
+            response = web.HTTPFound(location='/')
+            await remember(request, response, 'UserID')
+            raise response
+
+        async def logout(request):
+            response = web.HTTPFound(location='/')
+            await forget(request, response)
+            raise response
+
+        app = web.Application()
+        _setup(app, CookiesIdentityPolicy(), Autz())
+        app.router.add_route('GET', '/', index)
+        app.router.add_route('POST', '/login', login)
+        app.router.add_route('POST', '/logout', logout)
+
+        client = await aiohttp_client(app)
+        resp = await client.get('/')
+        assert web.HTTPUnauthorized.status_code == resp.status
+
+        await client.post('/login')
+        resp = await client.get('/')
+        assert web.HTTPOk.status_code == resp.status
+
+        await client.post('/logout')
+        resp = await client.get('/')
+        assert web.HTTPUnauthorized.status_code == resp.status
+
+
+async def test_check_authorized(loop, aiohttp_client):
     async def index(request):
-        return web.HTTPOk()
+        await check_authorized(request)
+        return web.Response()
 
     async def login(request):
         response = web.HTTPFound(location='/')
         await remember(request, response, 'UserID')
-        return response
+        raise response
 
     async def logout(request):
         response = web.HTTPFound(location='/')
         await forget(request, response)
-        return response
+        raise response
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/', index)
     app.router.add_route('POST', '/login', login)
     app.router.add_route('POST', '/logout', logout)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.get('/')
     assert web.HTTPUnauthorized.status_code == resp.status
 
@@ -194,38 +232,97 @@ async def test_login_required(loop, test_client):
     assert web.HTTPUnauthorized.status_code == resp.status
 
 
-async def test_has_permission(loop, test_client):
+async def test_has_permission(loop, aiohttp_client):
+
+    with pytest.warns(DeprecationWarning):
+
+        @has_permission('read')
+        async def index_read(request):
+            return web.Response()
+
+        @has_permission('write')
+        async def index_write(request):
+            return web.Response()
+
+        @has_permission('forbid')
+        async def index_forbid(request):
+            return web.Response()
+
+        async def login(request):
+            response = web.HTTPFound(location='/')
+            await remember(request, response, 'UserID')
+            return response
+
+        async def logout(request):
+            response = web.HTTPFound(location='/')
+            await forget(request, response)
+            raise response
+
+        app = web.Application()
+        _setup(app, CookiesIdentityPolicy(), Autz())
+        app.router.add_route('GET', '/permission/read', index_read)
+        app.router.add_route('GET', '/permission/write', index_write)
+        app.router.add_route('GET', '/permission/forbid', index_forbid)
+        app.router.add_route('POST', '/login', login)
+        app.router.add_route('POST', '/logout', logout)
+        client = await aiohttp_client(app)
+
+        resp = await client.get('/permission/read')
+        assert web.HTTPUnauthorized.status_code == resp.status
+        resp = await client.get('/permission/write')
+        assert web.HTTPUnauthorized.status_code == resp.status
+        resp = await client.get('/permission/forbid')
+        assert web.HTTPUnauthorized.status_code == resp.status
+
+        await client.post('/login')
+        resp = await client.get('/permission/read')
+        assert web.HTTPOk.status_code == resp.status
+        resp = await client.get('/permission/write')
+        assert web.HTTPOk.status_code == resp.status
+        resp = await client.get('/permission/forbid')
+        assert web.HTTPForbidden.status_code == resp.status
+
+        await client.post('/logout')
+        resp = await client.get('/permission/read')
+        assert web.HTTPUnauthorized.status_code == resp.status
+        resp = await client.get('/permission/write')
+        assert web.HTTPUnauthorized.status_code == resp.status
+        resp = await client.get('/permission/forbid')
+        assert web.HTTPUnauthorized.status_code == resp.status
+
+
+async def test_check_permission(loop, aiohttp_client):
 
-    @has_permission('read')
     async def index_read(request):
-        return web.HTTPOk()
+        await check_permission(request, 'read')
+        return web.Response()
 
-    @has_permission('write')
     async def index_write(request):
-        return web.HTTPOk()
+        await check_permission(request, 'write')
+        return web.Response()
 
-    @has_permission('forbid')
     async def index_forbid(request):
-        return web.HTTPOk()
+        await check_permission(request, 'forbid')
+        return web.Response()
 
     async def login(request):
         response = web.HTTPFound(location='/')
         await remember(request, response, 'UserID')
-        return response
+        raise response
 
     async def logout(request):
         response = web.HTTPFound(location='/')
         await forget(request, response)
-        return response
+        raise response
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, CookiesIdentityPolicy(), Autz())
     app.router.add_route('GET', '/permission/read', index_read)
     app.router.add_route('GET', '/permission/write', index_write)
     app.router.add_route('GET', '/permission/forbid', index_forbid)
     app.router.add_route('POST', '/login', login)
     app.router.add_route('POST', '/logout', logout)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
 
     resp = await client.get('/permission/read')
     assert web.HTTPUnauthorized.status_code == resp.status
diff --git a/tests/test_jwt_identity.py b/tests/test_jwt_identity.py
index bb5dd02..813deac 100644
--- a/tests/test_jwt_identity.py
+++ b/tests/test_jwt_identity.py
@@ -35,7 +35,7 @@ async def test_no_pyjwt_installed(mocker):
         JWTIdentityPolicy('secret')
 
 
-async def test_identify(loop, make_token, test_client):
+async def test_identify(loop, make_token, aiohttp_client):
     kwt_secret_key = 'Key'
 
     token = make_token({'login': 'Andrew'}, kwt_secret_key)
@@ -46,17 +46,17 @@ async def test_identify(loop, make_token, test_client):
         assert 'Andrew' == identity['login']
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, JWTIdentityPolicy(kwt_secret_key), Autz())
     app.router.add_route('GET', '/', check)
 
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     headers = {'Authorization': 'Bearer {}'.format(token.decode('utf-8'))}
     resp = await client.get('/', headers=headers)
     assert 200 == resp.status
 
 
-async def test_identify_broken_scheme(loop, make_token, test_client):
+async def test_identify_broken_scheme(loop, make_token, aiohttp_client):
     kwt_secret_key = 'Key'
 
     token = make_token({'login': 'Andrew'}, kwt_secret_key)
@@ -71,11 +71,11 @@ async def test_identify_broken_scheme(loop, make_token, test_client):
 
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     _setup(app, JWTIdentityPolicy(kwt_secret_key), Autz())
     app.router.add_route('GET', '/', check)
 
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     headers = {'Authorization': 'Token {}'.format(token.decode('utf-8'))}
     resp = await client.get('/', headers=headers)
     assert 400 == resp.status
diff --git a/tests/test_no_auth.py b/tests/test_no_auth.py
index 55051df..c995da4 100644
--- a/tests/test_no_auth.py
+++ b/tests/test_no_auth.py
@@ -2,21 +2,21 @@ from aiohttp import web
 from aiohttp_security import authorized_userid, permits
 
 
-async def test_authorized_userid(loop, test_client):
+async def test_authorized_userid(loop, aiohttp_client):
 
     async def check(request):
         userid = await authorized_userid(request)
         assert userid is None
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     app.router.add_route('GET', '/', check)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.get('/')
     assert 200 == resp.status
 
 
-async def test_permits(loop, test_client):
+async def test_permits(loop, aiohttp_client):
 
     async def check(request):
         ret = await permits(request, 'read')
@@ -27,8 +27,8 @@ async def test_permits(loop, test_client):
         assert ret
         return web.Response()
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     app.router.add_route('GET', '/', check)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.get('/')
     assert 200 == resp.status
diff --git a/tests/test_no_identity.py b/tests/test_no_identity.py
index 208521b..c86eb48 100644
--- a/tests/test_no_identity.py
+++ b/tests/test_no_identity.py
@@ -2,15 +2,15 @@ from aiohttp import web
 from aiohttp_security import remember, forget
 
 
-async def test_remember(loop, test_client):
+async def test_remember(loop, aiohttp_client):
 
     async def do_remember(request):
         response = web.Response()
         await remember(request, response, 'Andrew')
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     app.router.add_route('POST', '/', do_remember)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.post('/')
     assert 500 == resp.status
     assert (('Security subsystem is not initialized, '
@@ -18,15 +18,15 @@ async def test_remember(loop, test_client):
             resp.reason)
 
 
-async def test_forget(loop, test_client):
+async def test_forget(loop, aiohttp_client):
 
     async def do_forget(request):
         response = web.Response()
         await forget(request, response)
 
-    app = web.Application(loop=loop)
+    app = web.Application()
     app.router.add_route('POST', '/', do_forget)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.post('/')
     assert 500 == resp.status
     assert (('Security subsystem is not initialized, '
diff --git a/tests/test_session_identity.py b/tests/test_session_identity.py
index d6377a8..b697f93 100644
--- a/tests/test_session_identity.py
+++ b/tests/test_session_identity.py
@@ -20,14 +20,14 @@ class Autz(AbstractAuthorizationPolicy):
 
 
 @pytest.fixture
-def make_app(loop):
-    app = web.Application(loop=loop)
+def make_app():
+    app = web.Application()
     setup_session(app, SimpleCookieStorage())
     setup_security(app, SessionIdentityPolicy(), Autz())
     return app
 
 
-async def test_remember(make_app, test_client):
+async def test_remember(make_app, aiohttp_client):
 
     async def handler(request):
         response = web.Response()
@@ -37,12 +37,12 @@ async def test_remember(make_app, test_client):
     async def check(request):
         session = await get_session(request)
         assert session['AIOHTTP_SECURITY'] == 'Andrew'
-        return web.HTTPOk()
+        return web.Response()
 
     app = make_app()
     app.router.add_route('GET', '/', handler)
     app.router.add_route('GET', '/check', check)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.get('/')
     assert 200 == resp.status
 
@@ -50,7 +50,7 @@ async def test_remember(make_app, test_client):
     assert 200 == resp.status
 
 
-async def test_identify(make_app, test_client):
+async def test_identify(make_app, aiohttp_client):
 
     async def create(request):
         response = web.Response()
@@ -66,7 +66,7 @@ async def test_identify(make_app, test_client):
     app = make_app()
     app.router.add_route('GET', '/', check)
     app.router.add_route('POST', '/', create)
-    client = await test_client(app)
+    client = await aiohttp_client(app)
     resp = await client.post('/')
     assert 200 == resp.status
 
@@ -74,28 +74,28 @@ async def test_identify(make_app, test_client):
     assert 200 == resp.status
 
 
-async def test_forget(make_app, test_client):
+async def test_forget(make_app, aiohttp_client):
 
     async def index(request):
         session = await get_session(request)
-        return web.HTTPOk(text=session.get('AIOHTTP_SECURITY', ''))
+        return web.Response(text=session.get('AIOHTTP_SECURITY', ''))
 
     async def login(request):
         response = web.HTTPFound(location='/')
         await remember(request, response, 'Andrew')
-        return response
+        raise response
 
     async def logout(request):
         response = web.HTTPFound('/')
         await forget(request, response)
-        return response
+        raise response
 
     app = make_app()
     app.router.add_route('GET', '/', index)
     app.router.add_route('POST', '/login', login)
     app.router.add_route('POST', '/logout', logout)
 
-    client = await test_client(app)
+    client = await aiohttp_client(app)
 
     resp = await client.post('/login')
     assert 200 == resp.status