C++ Event-Driven Plug-in Framework. (Plug-in architecture III)

Tags: , , ,

This is the third article of a three-part series, I recommend reading the previous articles on plug-in architecture: Plug-in architecture (I): Preliminary notes and Plug-in architecture II: JavaScript Event-Driven plug-in framework.

In this third article on plug-in architecture I present the implementation of a server side plug-in framework in C++ using JavaScript as script language. It is built on top of Microsoft C++ REST SDK and uses Mozilla's JavaScript engine Spider Monkey as JavaScript interpreter. The design of the server architecture is practically identical to the design of the client side plug-in framework. So I won't describe in detail the framework in this article, if you are interested in technical details please read the documentation in GitHub.

Index

  1. Example code
  2. Documentation
  3. Server-side C++ plug-in framework overview

Example code

C++ Plug-in Server

Documentation

Server-side C++ plug-in framework overview

This is an event-driven architecture: Plug-ins listen to events. When an event is fired by a remote client or by the server application the plug-ins listening to that event run and return a response. The response is returned to the client or the server application. Plug-ins can communicate. And even if the logic is based on events, a plug-in can be run using its id. Plug-ins can extend other plug-ins.

C++ event-driven framework
C++ event-drive plug-in framework flows.

As for the client-side the server plug-in framework central piece is the Plug-in Handler, it manages the life-cycle of plug-ins during the application runtime, it loads plug-ins from repositories, adds, executes and removes plug-ins. The Plug-in Handler handles events and manages plug-in execution failures. It also manages the inter-plug-in communication and their communication with other entities as well as plug-in extensiveness.

As said in precedent articles, “a plug-in is an object with a defined structure that extends an application. Plug-ins can be added from different sources, server side applications, or loaded from files, we call these last plug-ins packaged plug-ins. Server side plug-ins are also portable and can be customized through properties. They can also extend other plug-ins. Not all of them may be executed, some can just have useful functions or wait to be extended by others. They can communicate.”

Firing plug-in events, running plug-ins, sending messages to plug-ins can be done remotely via HTTP requests. For interfacing with remote clients the plug-in framework will use a Plug-in Controller.

When running a plug-in in the server side a runner object is used. Runners are classes that have functions to run scripts or executables and return a response.

continue reading >>

C++ REST SDK OAuth 2.0 server

Tags: , ,

This article proposes an implementation of an authorization server in C++ using C++ REST SDK (Casablanca) based on the The OAuth 2.0 Authorization Framework specifications [1].

"OAuth defines four grant types: Authorization Code, Implicit, Resource Owner Password Credentials, and Client Credentials. It also provides an extension mechanism for defining additional grant types." [1] The proposed authorization server in this article implements the first two methods: "Authorization Code Grant" and "Implicit Grant", the other two are not implemented due to use and security reasons.

I present two coded examples one using native C++ objects to store data and another using Redis data structure server. The first option in my opinion must only be used for testing purposes, I strongly recommend the use of Redis.

I used sessions for managing resource access level. I recommend reading these previous articles: Standard structure for C++ REST SDK servers, and the one on sessions: C++ REST SDK sessions.

Contents


Code

This code has been tested on Linux and MacOS X. The server using Redis uses Alex Nekipelov Redis client library.

Example case

"A user owns messages posted on a board (for example a Twitter user). One day the user starts using an external application called client (example: Twitpic), this application needs to have access to the user's resources (his/her messages, contacts etc.). For having that access the client will need the authorization of the user. That is why Twitpic will use the Authorization server of Twitter so users can give permissions over their resources."

In the example case above we have introduced what we call roles in the Oauth 2.0 framework:
Client: Application requesting access to the user resources.
Resource owner: The user.
Authorization server: The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization.
Resource server: The server hosting the protected resources [,the user's resources], capable of accepting and responding to protected resource requests using access tokens. [1]

There are different ways for a user to authorize access to his/her resources to an application. I'm going to present two of them: The Authorization Code Grant and the Implicit Grant.

Authorization Code Grant

In this pattern client will request a code by the intermediation of the user. Once it has a code the client will be able to request access tokens to access the user's resources.

The authorization code provides a few important security benefits, such as the ability to authenticate the client, as well as the transmission of the access token directly to the client without passing it through the resource owner's user-agent and potentially exposing it to others, including the resource owner.[1]

Authorization Code Grant schema
Authorization Code Grant flow.

Let's explain how it works step by step:

The application includes a link or button that will redirect the user to the authorization server (A). The user will be redirected to an URL like this one:

https://cookinapps.io/oauth2/auth?response_type=code&client_id=NJCuH0kFykXJwrW0&redirect_uri=http%3A%2%2message-application.com%2Freadwritecode&scope=msg.select%20msg.insert%20msg.update%20msg.delete&state=rbJhShpRm0H85lypNJCuH0kFykXJwrW0
Let's have a look at the parameters in the query string of our URI:
response_type: Client is asking for a code that will allow it to ask for access_tokens to access the user's resources without having to ask the user each time it needs to access his resources.
client_id: Unique alphanumeric identifier of the client. It is generated when the client is registered and allowed to use the authorization system.
redirect_uri: URI where the user is going to be redirected after he/she authorize or denies the client to have access to his/her resources.
scope: Level of access the client ask to have over the user's resources. For example: “scope=msg.select msg.insert msg.update msg.delete” indicates the client wants to have the authorization to read, create, edit and delete user's messages.
state: Random alphanumeric string to prevent CSRF attacks [3, What is a CSRF token and how does it work?]. It has a timeout.

NOTE: We don't include a link to the authorization server URL, we include a link to an URL of the application and the application redirects the user to the authorization server, this will prevent the state to timeout.

Once the user is redirected to the authorization server, he/she can authorize or deny the client (B).

OAuth 2.0 authorization allow client

If the user is not logged into our authorization server an authentication form is displayed:

OAuth 2.0 user authentication login form

After the user authorizes or not the client, the user is redirected to the redirect_uri if the given URI is correct, if it is not no redirection will happen.

The authorization server validates the request to ensure that all required parameters are present and valid. If the request is valid, the authorization server authenticates the resource owner and obtains an authorization decision (by asking the resource owner or by establishing approval via other means).

When a decision is established, the authorization server directs the user-agent to the provided client redirection URI using an HTTP redirection response, or by other means available to it via the user-agent.[1]

Once the client has a code (C) it can directly request an access_token to the authorization server (D) making a POST request to "https://cookinapps.io/oauth2/auth" with a body like this one:

grant_type=authorization_code&code=r70Uiw3t2Kc2uztTY4UZRSMmJj742pW2l48ugeYnsXTh096ZsYnvwFs5cTEKqyLh&redirect_uri=http://message-application.com/board&client_id=v3tGNlfvbD9gT4Kx&client_secret=message_application_password

Let's have a look to the URI query string parameters:
grant_type: In our case authorization code, as client use a code to get a token.
code: Code to get a token. See the code as an equivalent to a user authorization.
redirect_uri: URI where the user has been redirected after he/she has authorized or denied the client to have access to his/her resources.
client_id: Unique alphanumeric identifier of the client.
client_secret: Password of the client. Only known by the client and the authorization server. Generated when a client is registered. Note how, even if another entity has intercepted the client code, if it does not have the client_secret it cannot ask for an access_token.

The response containing the acces_token (E) will be something like:

{
     "access_token":"2OsKG3V72GBn84xkagrK2U8m0WXbSjmT9fDoR4gsVIOi9runLWde4QbS377V2Gm8",
     "token_type":"bearer",
     "expires_in":3600,
     "refresh_token":"8KaUxEl0O6OxdX81qN2gP2xMRgRHHMOcVeHJNbo9079QPXKY9oa2O2HDC1s6VH5I”
}


At this point the client has access to the user's resources using the received access token and can ask for another access token once the first one is no more valid using the refresh token or using the code.

Implicit Grant

The implicit grant is a simplified authorization code flow optimized for clients implemented in a browser using a scripting language such as JavaScript. In the implicit flow, instead of issuing the client an authorization code, the client is issued an access token directly.[1]

( i ) In the schema and the coded examples I implemented a very basic case of a Javascript application to illustrate the Implicit Grant schema.

Implicit Grant schema
Implicit Grant flow.

The application redirects the user to an URL (A) like this one:

https://cookinapps.io/oauth2/auth?response_type=token&client_id=Rs08ANVMEmBBGPn0&redirect_uri=http%3A%2%2message-application.com%2Fread&scope=msg.select&state=F9M3awWKDz13JPha

The query string parameters passed in the URI are:
response_type: In this case we want a token.
client_id: Unique alphanumeric identifier of the client.
redirect_uri: URI where the user is going to be redirected after he/she authorize or denies the client to have access to his/her resources.
scope: The level of access the client wants over the user's resources. For example: “scope=msg.select” indicates the client wants to be authorized to read user's messages.
state: Random alphanumeric string to prevent CSRF attacks [3, What is a CSRF token and how does it work?].

The user will have to authorize or deny the client (B) (as for an authorization code grant). Once authorized or denied and if the redirect URI is correct the user is redirected to the client with an URI containing the access token (C,D) or an error:

http://message-application.com/read?access_token=yWE7KPw5KXn20mz4kGjS7ftVg44YhAlA3Qvs4Moo0mNruXULL38nBnUjUsXu37yK&token_type=bearer&scope=msg.select&state=F9M3awWKDz13JPha
error example:
http://message-application.com/read?state=V8FNbDArcnxzo0FBUVhWKTQUUy3NrVq9&error=access_denied&error_description=The%20resource%20owner%20or%20authorization%20server%20denied%20the%20request.
If the URI contains the access_token a script provided by the application will retrieve the access token (E), give it to the client so it is used to access the user's resources.

I have explained how authorization works, but I have been talking about users and clients as if they where known by the authorization server. Before a user authorize a client using our authorization server, the client (application) and the user (resource owner) have to be registered in our server:

Clients and Users registration

Client and user registration is not part of the authorization flow per se.

( i ) In the coded examples these two entities are named Oauth 2.0 Client and Oauth 2.0 User.

Client (Application) registration

Clients must be registered so they can use our authorization server and get access to users' resources.

Typically these are the fields of a client:
client_id: Alphanumeric string unique identifier, never invented by the client, generated by the client registration server.
password: Alphanumeric string, never invented by the client, generated by the client registration server.
client_type: public or confidential. Decided by the client registration server [1, 2.1 Client types].
application_name: A description field, given by the client.
redirect_uri(s): List of URIs that can be used to redirect the user after he/her authorizes the client.
roles: Maximum level of access over the user's resources a client can request. Chosen by the client, limited by the client registration server. A client is authorized to have a limited maximum access to the user's resources, it cannot ask for more access than it can have.

We can also imagine including other fields like a picture of the client, so it is shown when asking authorization, a text with conditions etc.

( i ) In the example we don't use a form to register clients, we do it before asking an access grant, but this shouldn't be done like this, we should register clients manually for more security or have a form to register clients like google has in the Google Developers Console.

Note how the client ID and client secret that are known to both our authorization server and the application should be generated by the registration server, not chosen by the client. This is done to prevent the existence of weak passwords.

The fields validation and restrictions should be guarantied by the client registration server.

User (Resource owner) registration

As said before user is the resource owner and the person that authorizes or denies the client to access his/her resources. Our authorization server must know the user.

Usually users are created with the help of a public form.

Typically a user has these fields:
username: Unique alphanumeric string invented by the user.
password: Alphanumeric string invented by the user or given by the user registration server.
roles: Maximum level of access the user can authorize a client to have. Assigned by the user registration server.

The fields validation and restrictions should be guarantied by the user registration server.

We have explained the authorization flows, and the main entities in the game and how they interact. Now let's explain what happens in the server side. How does a resource server knows the access level that a client has by using a certain access_token? Answer is: with sessions.

Sessions

Sessions are server side objects identified with a token.

A session does not have an owner. They can be “used” by anyone that holds its token.

Sessions have a number of roles. Session Roles can be used to manage permissions over resources and to store useful properties.

IF (session has role X) THEN permit;

OAuth 2.0 Session
Session entity and its members and example values.

Is there a relation between the OAuth 2.0 scope parameter and the session roles?
Yes, when a client request an access_token a new session is opened. The client ask to have a level of access through the scope parameter, this scope is transformed to roles assigned to the session. See the flow diagram below:

Access token request scope
From scope parameter to session roles. From session token to access_token.

( i ) How do we know if a client is allowed to request a certain level of access?
1. Checking that the user authorizing the client is allowed to give this access level to a client by checking the user's role list.
2. Checking that the registered client has the requested access level between its role list.

IF (oauth2_client has roles && oauth2_user can give roles) THEN open session with requested roles;

When a client requests to have access to a user resource, for example requests a list of user messages, the client makes its request attaching an access_token, this access_token allows the resource server to retrieve a session, this session has roles, if the session has the roles permitting to read the user messages the client will receive the list of messages, but if the session does not have the roles permitting to read user messages the client will receive an error as response:

OAuth 2.0 Access token to session
From access_token to resource access using session roles.

In the resource server the code will be:

// retrieve session with given token:
Session session(access_token);
// check if the session has the role that will give the
// owner of the access token the permission to read
// user's messages:
if (session.roles()->Is(“msg.select”)){
	// now get a session role property called “username”
	std::string username = session.roles()->GetProperty(“msg.select”,”username”);
	// retrieve messages related to the user with username.
	// ...
}else{
	// Hey the session does not have the “msg.select” role,
	// The owner of the access_token does not have permission
	// to read user messages!!!
}


At this point we have an idea of the authorization flow, the communication client-authorization server and client-resource server and what happens in the server side, how the access to resources is managed with sessions. In the next section I present the structure of an OAuth 2.0 server in C++, built using Microsoft C++ REST SDK (Casablanca) library.

C++ REST SDK (Casablanca) Integration

The C++ REST SDK is a Microsoft project for cloud-based client-server communication in native code using a modern asynchronous C++ API design. This project aims to help C++ developers connect to and interact with services.[4]

This article attach an example implementing an OAuth 2.0 server using C++ REST SDK. I recommend having a look at it to have a better understanding of this section.

Here I present a schema resuming the entities and flows:

OAuth 2.0 c++ server
Entities used in the c++ server.

Classes

OAuth2Controller: Receives the authorizations requests from clients, redirects the users back to the client's URI, deliver codes, access_tokens, refresh_tokens to the clients. The controller can also give information about the clients authorized by the users, and allows a user to remove his/her given authorizations. This controller also allows a user to logout from the authorization server. Reference Documentation.

OAuth2Parameters: Used for containing the request or response parameters such as grant_type, access_token, client_id, state, scope... Has some parsing functions to parse URIs with the parameters and anothers to encode them like:

?access_token=yWE7KPw5KXn20mz4kGjS7ftVg44YhAlA3Qvs4Moo0mNruXULL38nBnUjUsXu37yK&token_type=bearer&scope=msg.select&state=F9M3awWKDz13JPha
or JSON like:
{
     "access_token":"2OsKG3V72GBn84xkagrK2U8m0WXbSjmT9fDoR4gsVIOi9runLWde4QbS377V2Gm8",
     "token_type":"bearer",
     "expires_in":3600,
     "refresh_token":"8KaUxEl0O6OxdX81qN2gP2xMRgRHHMOcVeHJNbo9079QPXKY9oa2O2HDC1s6VH5I”
}
Reference documentation

OAuth2Entity: Mother class of OAuth2Client, OAuth2User, OAuth2Code and OAuth2Authorization. Reference documentation.

OAuth2Client: Class representing the third party application that request access to the user resources.

OAuth2User: Class representing the owner of the resources. Should only be used for authentication and authorization purposes. It does not have information about its resources, it should not be used by the resource server.

OAuth2Code: This class represents a code generated in a code request, is the equivalent to an authorization, with a code a client can ask for access_tokens.

OAuth2Authorization: The model. Authorization has the function of grouping a user, a client a code and an access_token and the function of managing the logic and rules of the authorization requests and access_token requests as well as deleting authorizations and getting information about the clients authorized by a user.

OAuth2Factory: Used to create OAuth 2.0 entities, an OAuth2Factory is passed to the OAuth2Controller and the OAuth2Authorization so we can instantiate derived classes of OAuth2Entity.

Session: A session is identified with a token and contains the level of access to a user resource. The only entity shared by the authorization server and the resource server.

SessionFactory: Used to create Sessions, it is passed to the OAuth2Controller and the OAuth2Authorization so we can instantiate derived classes of Session.

Derived classes: We use derived classes of the OAuth2Entities and the Sessions. Because each Oauth 2.0 entity or session can use a particular storage system, a particular id generator or a particular cryptograph to authenticate clients and users. You will see that in the provided examples one is coded using SimpleOauth2Entities and MapSessions that store data in maps and another is coded using RedisOauth2Entities and RedisSessions that use Redis as storage system. This classes are instantiated through the Oauth2Factory and the SessionFactory without knowing their type, benefiting from polymorphism.

Oauth 2.0 controller

Creation
Creating an OAuth 2.0 controller:

// Session Factory
// Used to instatiate derived classes of Session with the benefits
// of polymorphism. In this case we will be using sessions that are stored
// using a Redis storage system.
std::shared_ptr<granada::http::session::SessionFactory> session_factory(new granada::http::session::RedisSessionFactory());

// OAuth 2.0 Factory, will be used to instatiate derived classes
// of OAuth 2.0 entities, with the benefits of polymorphism.
// In this case derived OAuth 2.0 entities that use Redis as
// storage system.
std::shared_ptr<granada::http::oauth2::OAuth2Factory> oauth2_factory(new granada::http::oauth2::RedisOAuth2Factory());

// OAuth 2.0 Controller
// Session Factory and OAuth 2.0 Factory are passed
std::unique_ptr<granada::http::controller::OAuth2Controller> auth_controller(new granada::http::controller::OAuth2Controller(addr,session_factory,oauth2_factory));
Oauth 2.0 Controller methods:
GET:
- Request an authorization:
Request code example:
/oauth2/auth?response_type=code&client_id=NJCuH0kFykXJwrW0&redirect_uri=http%3A%2%2message-application.com%2Freadwritecode&scope=msg.select%20msg.insert%20msg.update%20msg.delete&state=rbJhShpRm0H85lypNJCuH0kFykXJwrW0
Request access_token example:
/oauth2/auth?response_type=token&client_id=Rs08ANVMEmBBGPn0&redirect_uri=http%3A%2%2message-application.com%2Fread&scope=msg.select&state=F9M3awWKDz13JPha
- Request Information: The user must be logged in the authorization server to request information about his/her authorizations.
/oauth2/info
The response will be a JSON with a list of clients authorized.
{
  "data": [
    {
      "application_name": "Super Board",
      "client_id": "4PcGNlfvbD9gT4Kx"
    },
    {
      "application_name": "Message Reader",
      "client_id": "Rs08ANVMEmBBGPn0"
    },
    {
      "application_name": "Message Writer Application",
      "client_id": "nrNhkrM6y7vRkjcN"
    }
  ]
}
- Logout: The user must be logged in the authorization server to logout.
/oauth2/logout
POST:
Request access_token with a code:
grant_type=authorization_code&code=r70Uiw3t2Kc2uztTY4UZRSMmJj742pW2l48ugeYnsXTh096ZsYnvwFs5cTEKqyLh&redirect_uri=http://message-application.com/board&client_id=v3tGNlfvbD9gT4Kx&client_secret=message_application_password
Request access_token with a refresh_token:
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA&redirect_uri=http://message-application.com/board&client_id=v3tGNlfvbD9gT4Kx&client_secret=message_application_password
DELETE:
Delete the authorization given by a user to a client. The user must be logged in the authorization server to make a deletion.
/oauth2?client_id=v3tGNlfvbD9gT4Kx
This will delete all the authorizations the user has given to the client with the given id.

Resource server

The logic in the resource server is quite simple, once a client send us an access_token via an HTTP request, we have to retrieve the session with the given token, as described in the session chapter and then allow access or not to the requested user resource.

// retrieve session with given token:
Session session(access_token);
// check if the session has the role that will give the
// owner of the access token the permission to read
// user's messages:
if (session.roles()->Is(“msg.select”)){
  // now get a session role property called “username”
  std::string username = session.roles()->GetProperty(“msg.select”,”username”);
  // retrieve messages related to the user with username.
  // ...

  // respond the client with the list of messages.
}else{
	// Hey the session does not have the “msg.select” role,
  // The owner of the access_token does not have permission
  // to read user messages!!!

  // respond the client with an error.
}

Server configuration properties

Thanks to server properties, we can configure the lengths of the tokens, codes, timeouts etc. This values can be customized using the server configuration file. If no value provided for a property a default value is taken from granada/defaults.dat.

(i) You will find a configuration file in the example
Example:

####
## OAuth 2.0 configuration
##
# the auth by default
oauth2_authorize_uri=auth
oauth2_logout_uri=logout
oauth2_authorizing_login_template=www/authorize/index.html
oauth2_authorizing_message_template=www/authorize/message.html
oauth2_authorizing_error_template=www/error.html

Properties that can be configured and default values taken if no custom value is found:

Property name Description Default value
oauth2_authorize_uriSub URI of authorization, example: authorize in that case the full URL for authorization will be my-domain.com/oauth2/authorizeauth
oauth2_logout_uriSub URI of logout user from authorization server, example: logout in that case the full URL for authorization will be my-domain.com/oauth2/logoutlogout
oauth2_info_uriSub URI of info user from authorization server, example: info in that case the full URL for authorization will be my-domain.com/oauth2/infoinfo
oauth2_authorizing_login_templateHTML to show in case the user is not already logged in our auth server,default value in granada/http/oauth2/oauth2.templates
oauth2_authorizing_message_templateShown in case the user is already logged in our auth server, we don't ask the user it's credentials, instead we show one button for authorizing the client and another for denying authorizationdefault value in granada/http/oauth2/oauth2.templates
oauth2_authorizing_error_templateLoad error page, in case, the url is not well formed this HTML will show.default value in granada/http/oauth2/oauth2.templates
oauth2_logout_templateHTML will show if user manually logout from the auth server..default value in granada/http/oauth2/oauth2.templates
oauth2_client_id_lengthLength of the client id.16
oauth2_client_value_namespaceName of the client's value namespace in the storage system.oauth2.client:value:
oauth2_user_value_namespaceName of the user's value namespace in the storage system.oauth2.user:value:
oauth2_code_lengthLength of the code.64
oauth2_code_value_namespaceName of the code's value namespace in the storage system.oauth2.code:value:
oauth2_use_refresh_tokenIf true when client request an access token a refresh token is also delivered.false
oauth2_authorization_namespaceName of the OAuth 2.0 authorization namespace in the storage system.oauth2.authorization:


Example

Code: here.
- The server using Redis uses Alex Nekipelov Redis client library.
- I used openssl for encryption as it is included in C++ REST SDK projects by default.
- You have to include granada library. It contains the OAuth 2.0 classes, OAuth 2.0 Controller class, default values, Session classes, Cryptographs and Cache drivers to access storage systems. Granada is built on top of C++ REST SDK, it proposes a typed structure for C++ REST SDK server applications, implements sessions, and utils to deal with data.

I used cmake to build the examples:

$: cmake .
$: make

Then execute redis-oauth2-server or oauth2-server as you wish and interact with the server typing http://localhost in your browser.

You must see something like:


Conclusions

I recommend reading the The OAuth 2.0 Authorization Framework specifications [1] and the Google Identity Platform [2] to pick some ideas.

In the proposed implementation Authorization Grant is done with user credentials, sessions but we could imagine other ways of granting access: for example using certificates.

We may consider varying the sessions' timeout depending on the type of client, that means establishing a difference between clients capable of maintaining the confidentiality of their credentials (confidential clients) and those clients incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application), and incapable of secure client authentication via any other means (public clients). [1, Client Types]

The reflexion above can be extended to limiting the codes, refresh tokens, client credentials etc. lifetimes.

In the examples I used maps or Redis to store the users' credentials, the sessions and the users' data, but we may want to use another storage system. In this case we will have to create a cache driver and create a session, session handler and session roles that will store their data using the new driver.

I strongly recommend to use Redis to manage the Oauth 2.0 entities data and sessions.

Oauth 2.0 specifications recommend using redirection and "application/x-www-form-urlencoded" format [1, Redirection Endpoint].

I used openssl for encryption as it is included in C++ REST SDK projects by default. But I recomend having a look to crypto++ or Botan libraries.

Security:
Do not use third party scripts in the redirection endpoint, when information is passed through the URI make sure no third party scripts are executed.

The URL used when authenticating a user should be accessible over SSL, and HTTP connections should be refused.

CSRF attacks: Use state parameter to prevent Cross-site request forgery attacks [3].

Read about security considerations: https://tools.ietf.org/html/rfc6749#section-10

References


Contact

Twitter
- Email: afernandez [at] cookinapps.io