Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication & authorization example #475

Open
mbnoimi opened this issue Aug 28, 2021 · 5 comments
Open

Authentication & authorization example #475

mbnoimi opened this issue Aug 28, 2021 · 5 comments
Labels
Question Further information is requested

Comments

@mbnoimi
Copy link

mbnoimi commented Aug 28, 2021

Most of common examples in web frameworks are Authentication & authorization examples (ex. django examples).
May you please add some examples regarding that.

@bamkrs
Copy link
Member

bamkrs commented Aug 31, 2021

Hello @mbnoimi
sorry for not hearing from us in 3 days. Both @lganzzzo and me are heavily working on Oat++ v1.3.0 and thus missed to reply.

We have several authorisation examples in our tests.
Basic-Authorization: https://github.com/oatpp/oatpp/blob/master/test/oatpp/web/app/BasicAuthorizationController.hpp
Bearer-Authorization: https://github.com/oatpp/oatpp/blob/master/test/oatpp/web/app/BearerAuthorizationController.hpp

For simple basic authorization you do only need to to two things:

  1. Set Authorization-Handler for Controller (we have a default Authorization Handler)
  2. Enable Authorization for Endpoint

Authorization Infrastructure

Authorization in Oat++ requires two classes: AuthorizationHandler and AuthorizationObjects.

When a request to an authorization-enabled endpoint is received. The request is first directed to the AuthorizationHandler.
A authorization handler's job is to search headers (or other sources) for credentials or tokens and parse them to an AuthorizationObject.
This AuthorizationObject is then passed to the endpoint.
Depending on the implementation, a AuthorizationObject can either be a simple credential-carrier or i.E. a "User-Object".

Oat++ ships with two very simple AuthorizationHandlers. One for Basic-Authorization and one for Bearer-Authorization.
Both handler just parse the information and pass them to the Endpoint.

However, you can implement your own AuthorizationHandler which queries i.E. an Database and passes i.E. a User-Object or whatever your use-case requires.

Simple Basic-Authorization

1: Set Authorization Handler

The simplest way is to use the default BasicAuthorizationHandler. The handler itself parses the respective headers and passes username and password to your Endpoint. It does not check any credentials.

  MyController(const std::shared_ptr<ObjectMapper>& objectMapper)
    : oatpp::web::server::api::ApiController(objectMapper)
  {
    // Use default BasicAuthorizationHandler for this controller. 
    setDefaultAuthorizationHandler(std::make_shared<oatpp::web::server::handler::BasicAuthorizationHandler>("default-test-realm"));
  }

2: Enable Authorization for your Endpoint

To enable Authorization for an Endpoint, you need to add a AUTHORIZATION parameter-macro to your endpoint for a DefaultBasicAuthorizationObject which is generated by the Authorization-Handler.

  ENDPOINT("GET", "auth", myAuthorizedEndpoint,
           AUTHORIZATION(std::shared_ptr<oatpp::web::server::handler::DefaultBasicAuthorizationObject>, authObject)) {

    // authObject contains userId and password retreived from the request headers.
    // Implement your own logic to check those credentials
    auto dto = TestDto::createShared();
    dto->testValue = authObject->userId + ":" + authObject->password;

    if(dto->testValue == "foo:bar") {
      return createDtoResponse(Status::CODE_200, dto);
    } else {
      return createDtoResponse(Status::CODE_401, dto);
    }

  }

Now you have a very simple authorization for your endpoint but have to check the credentials in every endpoint.

Custom AuthorizationHandler for DB-Queries or Similar

A much better approach is to use the AuthorizationHandler to actually handle the authorization for you and you do not need to do any additional checking in your Endpoints.

For this we have to implement our own AuthorizationHandler which does all the checking. The endpoint will only be called if the Authorization is valid.

1: Implement AuthorizationHandler

Lets say you use bearer-tokens for authorization. Since we use the default bearer-token headers and mechanisms, we can simply implement an AuthorizationHandler which inherits from oatpp::web::server::handler::BearerAuthorizationHandler and implementyour custom AuthorizationObject:

class MyAuthorizationObject: public oatpp::web::server::handler::AuthorizationObject {
 public:
  oatpp::String User;
  v_uint64 Id;
  oatpp::String Type;
};

class MyAuthorizationHandler : public oatpp::web::server::handler::BearerAuthorizationHandler {
 public:
  MyAuthorizationHandler()
    : oatpp::web::server::handler::BearerAuthorizationHandler("custom-bearer-realm") // Set realm in parent-constructor
  {}

  // override `authorize` function with your own logic
  // `authorize` gets called with the token received in the bearer-authorization header.
  std::shared_ptr<AuthorizationObject> authorize(const oatpp::String& token) override {

    /* Pseudo-Code Begin */
    // query your database or do whatever to validate the token
    auto myDatabaseResult = m_myDatabase->findToken(token);
    // check for a valid result
    if (myDatabaseResult) {
       // The token was found in the database, create our `AuthorizationObject`
       auto authObject = MyAuthorizationObject::createShared();
       authObject->User = myDatabaseResult->User;
       authObject->Id = myDatabaseResult->Id;
       authObject->Type = myDatabaseResult->Type;
    }

    // In case the token is invalid or the authorization fails, return nullptr and 
    // Oat++ will reject the request and will not call your endpoint
    return nullptr;
  }

};

2: Endpoint

With our custom AuthorizationHandler, this endpoint is only called when the Authorization is valid so we do not need to check any additional stuff

  ENDPOINT("GET", "auth", myAuthorizedEndpoint,
           AUTHORIZATION(std::shared_ptr<MyAuthorizationObject>, authObject)) {

    return createResponse(Status::CODE_200, "Hello " + authObject->User);
  }

@bamkrs bamkrs added the Question Further information is requested label Sep 6, 2021
@beuschl
Copy link

beuschl commented Jan 8, 2022

Can you also provide an example how to use the AUTHORIZATION macro with the API_CLIENT? I figured it out for ENDPOINTS (controller) but not for CLIENTS. I also found no example in github for this :(

@lganzzzo
Copy link
Member

Hello @beuschl ,

You can find examples in oatpp tests here - https://github.com/oatpp/oatpp/blob/master/test/oatpp/web/app/Client.hpp#L67

  API_CALL("GET", "default-basic-authorization", defaultBasicAuthorization, AUTHORIZATION_BASIC(String, authString))
  API_CALL("GET", "default-basic-authorization", defaultBasicAuthorizationWithoutHeader)
  API_CALL("GET", "basic-authorization", customBasicAuthorization, AUTHORIZATION_BASIC(String, authString))
  API_CALL("GET", "basic-authorization", customBasicAuthorizationWithoutHeader)

  API_CALL("GET", "bearer-authorization", bearerAuthorization, AUTHORIZATION(String, authString, "Bearer"))

@Jusedawg
Copy link

This works great for creating an handler for a token or simply verifying a userid/password, but what about adding roles/permissions to the Authorization macro on each endpoint?

i.e. using .Net as a pattern to show what I mean. https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0

We can obviously check roles/permissions inside the handler, but need a way to assign them to the endpoints since the authorize method is being overidden.

example:

  ENDPOINT("GET", "auth", myAuthorizedEndpoint,
           AUTHORIZATION(std::shared_ptr<MyAuthorizationObject>, authObject, [requiredRoles], [requiredPermissions])) {

    return createResponse(Status::CODE_200, "Hello " + authObject->User);
  }

Just trying to come up with a clean and easy way to add them to each endpoint in the controllers. If we can tell which method/endpoint is calling the handler, we can probably just have them mapped in the database and look them up that way.

@RichardJECooke
Copy link

Does Oat++ have a social authentication provider? Like for oauth2, Facebook, Google, etc. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question Further information is requested
Projects
None yet
Development

No branches or pull requests

6 participants