Project: userver (App Definition and Development / Streaming & Messaging)
File: auth_postgres.md

Make sure that you can compile and run core tests and read a basic example @ref scripts/docs/en/userver/tutorial/hello_service.md. This tutorial shows how to create a custom authorization check by tokens with scopes for HTTP handlers. Required scopes are specified in static configuration file for each HTTP handler. In the tutorial the authorization data is cached in components::PostgreCache, and information of an authorized user (it's name) is passed to the HTTP handler. Creation of tokens and user registration is out of scope of this tutorial. @warning Authorization scheme from this sample is vulnerable to the MITM-attack unless you are using a secure connection (HTTPS). Let's make a table to store users data: @include samples/postgres_auth/schemas/postgresql/auth/migrations.txt @include samples/postgresauth/schemas/postgresql/auth/migrations/V001createdb.sql @include samples/postgresauth/schemas/postgresql/auth/migrations/V002addname.sql Authorization data is rarely changed and often queried. Caching it would improve response times: @snippet samples/postgresauth/userinfo_cache.hpp user info cache Cache configuration is straightforward: @snippet samples/postgresauth/staticconfig.yaml pg-cache config To implement an authorization checker derive from server::handlers::auth::AuthCheckerBase and override the virtual functions: @snippet samples/postgresauth/authbearer.cpp auth checker declaration The authorization should do the following steps: check for "Authorization" header, return 401 if it is missing; @snippet samples/postgresauth/authbearer.cpp auth checker definition 1 check for "Authorization" header value to have `Bearer some-token` format, return 403 if it is not; @snippet samples/postgresauth/authbearer.cpp auth checker definition 2 check that the token is in the cache, return 403 if it is not; @snippet samples/postgresauth/authbearer.cpp auth checker definition 3 check that user has the required authorization scopes, return 403 if not; @snippet samples/postgresauth/authbearer.cpp auth checker definition 4 if everything is fine, then store user name in request context and proceed to HTTP handler logic. @snippet samples/postgresauth/authbearer.cpp auth checker definition 5 @warning `CheckAuth` functions are invoked concurrently on the same instance of the class. In this sample the `AuthCheckerBearer` class only reads the class data. @ref scripts/docs/en/userver/synchronization.md "synchronization primitives" should be used if data is mutated. Authorization checkers are produced by authorization factories derived from server::handlers::auth::AuthCheckerFactoryBase: @snippet samples/postgresauth/authbearer.hpp auth checker factory decl Factories work with component system and parse handler-specific authorization configs: @snippet

auth checker factory definition Each factory should be registered at the beginning of the `main()` function via server::handlers::auth::RegisterAuthCheckerFactory function call: @snippet samples/postgresauth/postgresservice.cpp auth checker registration That factory is invoked on each HTTP handler with the matching authorization type: @snippet samples/postgresauth/staticconfig.yaml hello config Data that was set by authorization checker could be retrieved by handler from server::request::RequestContext: @snippet samples/postgresauth/postgresservice.cpp request context Aside from calling server::handlers::auth::RegisterAuthCheckerFactory for authorization factory registration, the `main()` function should also construct the component list and start the daemon: @snippet samples/postgresauth/postgresservice.cpp main To build the sample, execute the following build steps at the userver root directory: ``` mkdir build_release cd build_release cmake -DCMAKEBUILDTYPE=Release .. make userver-samples-postgres_auth ``` The sample could be started by running `make start-userver-samples-postgres_auth`. The command would invoke @ref scripts/docs/en/userver/functional_testing.md "testsuite start target" that sets proper paths in the configuration files, prepares and starts the DB, and starts the service. To start the service manually start the DB server and run `./samples/postgresservice/userver-samples-postgresauth -c </path/to/static_config.yaml>`. Now you can send a request to your service from another terminal: ``` bash $ curl 'localhost:8080/v1/hello' -i HTTP/1.1 401 Unauthorized Date: Wed, 21 Dec 2022 13:04:17 UTC Content-Type: text/html X-YaRequestId: dbc9dbaa3fc04ce8a86b27a1aa582cd6 X-YaSpanId: aa573144f2312714 X-YaTraceId: 4dfb9e852e07473c9d57a8eb520e7965 Server: userver/2.0 (20221221124812; rv:unknown) Connection: keep-alive Content-Length: 28 Empty 'Authorization' header $ curl -H 'Authorization: Bearer SOMEWRONGUSER_TOKEN' 'localhost:8080/v1/hello' -i HTTP/1.1 403 Forbidden Date: Wed, 21 Dec 2022 13:06:06 UTC Content-Type: text/html X-YaRequestId: 6e39f3bf27324aa3acb01a30b9653b2d X-YaTraceId: e5d38ab53b3f495a9b97279a731f5fde X-YaSpanId: e64b939c37035d88 Server: userver/2.0 (20221221124812; rv:unknown) Connection: keep-alive Content-Length: 0 ``` @ref scripts/docs/en/userver/functional_testing.md "Functional tests" for the service could be implemented using the testsuite. To do that you have to: Provide PostgreSQL schema to start the database: @ref samples/postgres_auth/schemas/postgresql/auth/migrations.txt @ref samples/postgresauth/schemas/postgresql/auth/migrations/V001createdb.sql @ref samples/postgresauth/schemas/postgresql/auth/migrations/V002addname.sql Tell the testsuite to start the PostgreSQL database by adjusting the @ref samples/postgres_auth/tests/conftest.py Prepare the DB test data @ref samples/postgresauth/tests/static/testdata.sql Write the test: @snippet samples/postgresauth/tests/testpostgres.py Functional test See the full example: @ref samples/postgresauth/userinfo_cache.hpp @ref samples/postgresauth/authbearer.hpp @ref samples/postgresauth/authbearer.cpp @ref samples/postgresauth/postgresservice.cpp @ref samples/postgresauth/staticconfig.yaml @ref samples/postgres_auth/CMakeLists.txt @ref samples/postgres_auth/schemas/postgresql/auth/migrations.txt @ref samples/postgresauth/schemas/postgresql/auth/migrations/V001createdb.sql @ref samples/postgresauth/schemas/postgresql/auth/migrations/V002addname.sql @ref samples/postgres_auth/tests/conftest.py @ref samples/postgresauth/tests/testpostgres.py @ref samples/postgresauth/tests/static/testdata.sql @htmlonly <div class="bottom-nav"> @endhtmlonly @ref scripts/docs/en/userver/tutorial/redisservice.md | @ref scripts/docs/en/userver/tutorial/jsonto_yaml.md @htmlonly </div> @endhtmlonly @example samples/postgresauth/userinfo_cache.hpp @example samples/postgresauth/authbearer.hpp @example samples/postgresauth/authbearer.cpp @example samples/postgresauth/postgresservice.cpp @example samples/postgresauth/staticconfig.yaml @example samples/postgres_auth/CMakeLists.txt @example samples/postgres_auth/schemas/postgresql/auth/migrations.txt @example samples/postgresauth/schemas/postgresql/auth/migrations/V001createdb.sql @example samples/postgresauth/schemas/postgresql/auth/migrations/V002addname.sql @example samples/postgres_auth/tests/conftest.py @example samples/postgresauth/tests/testpostgres.py @example samples/postgresauth/tests/static/testdata.sql