How to use

The oauthclient module provides OAuth web authorization support in Invenio.

OAuth client support is typically used to allow features such as social login (e.g. Sign in with Twitter) and access to resources owner by a specific user at a remote service. Both OAuth 1.0 and OAuth 2.0 are supported.

The module contains:

  • Views: OAuth login and authorized endpoints, linked account settings and sign-up handling.
  • Client: A client to interact with remote applications.
  • Contrib: Ready-to-use GitHub and ORCID remote applications.
  • Models: Persistence layer for OAuth access tokens including support for storing extra data together with a token.
  • Handlers: Customizable handlers for deciding what happens when a user authorizes a request.

Authorization Flow Overview

OAuth 2.0 defines several possible authorization flows depending on the type of client you are authorizing (e.g. web application, browser-based app or mobile apps). The web application client is the only authorization flow supported by this module.

A typical web application authorization flow involves the following roles:

  • Client (i.e. a third-party application in this case your Invenio instance).
  • Resource server (i.e. the remote service).
  • Resource owner (i.e. the user).

The web application authorization flow is used to e.g. allow sign in with service X. The end result of a completed authorization flow is an access token which allows the client to access a resource owner’s resources on the resource server.

Before the authorization flow is started, the client must be registered with the resource server. The resource server will provide a client key and client secret to the client. Following is an example of the authorization flow with ORCID:

  1. The resource owner (i.e. the user) clicks “Sign in with ORCID”:

    GET /oauth/login/orcid/ HTTP/1.1
    

    The client redirects the user to the resource server’s authorize URL.

    HTTP/1.1 302 FOUND
    Location: https://orcid.org/oauth/authorize?response_type=code&
      client_id=<CLIENT KEY>&
      redirect_uri=https://localhost/oauth/authorized/orcid/&
      scope=/authenticate&
      state=...
    

Note, following query parameters in the authorize URL:

  • response_type - Must be code for web application flow (named authorization code grant).
  • client_id - The client key provided by the resource server when the client was registered.
  • redirect_uri - The URL the resource server will redirect the resource owner back to after having authorized the request. Usually the redirect URL must be provided when registering the client application with the resource server.
  • scope - Defines the level of access (defined by the resource server)
  • state - A token to mitigate against cross-site request forgery (CRSF). In Invenio this state is a JSON Web Signature (JWS) that by default expires after 5 minutes.
  1. The resource server asks the user to sign-in (if not already signed in).

  2. The resource server asks the resource owner to authorize or reject the client’s request for access.

  3. If the resource owner authorizes the request, the resource server redirects the resource owner back to the client web application (using the redirect_uri provided in step 1):

    HTTP/1.1 302 FOUND
    Location: https://localhost/oauth/authorized/orcid/?code=<CODE>&
      state=...
    

    Included in the redirect is a one-time auth code which is typically only valid for short time (seconds), as well as the state token initially provided.

  4. The client now exchanges the one time auth code for an access token using the resource server’s access token URL:

    POST https://pub.orcid.org/oauth/token HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code&
    code=<CODE>&
    redirect_uri=<REDIRECT_URI>&
    client_id=<CLIENT KEY>&
    client_secret=<CLIENT SECRET>
    

    The resource server replies with an access token:

    {"access_token": "<ACCESS TOKEN>"}
    

    The client stores the access token, and can use it to make authenticated requests to the resource server:

    GET https://api.example.org/ HTTP/1.1
    Authorization: Bearer <ACCESS TOKEN>
    

Further reading:

Usage

  1. Edit your configuration. Ensure invenio_oauthclient is included in PACKAGES (by default it’s included).

  2. Define remote resource serves in the OAUTHCLIENT_REMOTE_APPS.

    PACKAGES = [
        # ...
        'invenio_oauthclient',
        # ...
    ]
    
    OAUTHCLIENT_REMOTE_APPS = dict(
        # ...
    )
    

Configuration variables for defining remote applications.

OAUTHCLIENT_REMOTE_APPS Dictionary of remote applications. See example below. Default: {}.
OAUTHCLIENT_SESSION_KEY_PREFIX Prefix for the session key used to store the an access token. Default: oauth_token.
OAUTHCLIENT_STATE_EXPIRES Number of seconds after which the state token expires. Defaults to 300 seconds.
OAUTHCLIENT_REMOTE_APP Replaces the default remote application class.

Each remote application must be defined in the OAUTHCLIENT_REMOTE_APPS dictionary, where the keys are the application names and the values the configuration parameters for the application.

OAUTHCLIENT_REMOTE_APPS = dict(
    myapp=dict(
        # configuration values for myapp ...
    ),
)

The application name is used in the login, authorized, sign-up and disconnect endpoints:

  • Login endpoint: /oauth/login/<REMOTE APP>/.
  • Authorized endpoint: /oauth/authorized/<REMOTE APP>/.
  • Disconnect endpoint: /oauth/disconnect/<REMOTE APP>/.
  • Sign up endpoint: /oauth/login/<REMOTE APP>/.

Remote application

Configuration of a single remote application is a dictionary with the following keys:

  • title - Title of remote application. Displayed to end-users under Account > Linked accounts.
  • description - Short description of remote application. Displayed to end-users under Account > Linked accounts.
  • icon - CSS class for icon of service (e.g. fa fa-github for using the Font-Awesome GitHub icon). Displayed to end-users.
  • params - Flask-OAuthlib remote application parameters..
  • authorized_handler - Import path to authorized callback handler.
  • disconnect_handler - Import path to disconnect callback handler.
  • signup_handler - A dictionary of import path to sign up callback handler.
  • remember - Boolean indicating if the session should be permament.
OAUTHCLIENT_REMOTE_APPS = dict(
    myapp=dict(
        title='...',
        description='...',
        icon='...',
        authorized_handler="...",
        disconnect_handler="...",
        signup_handler=dict(
            info="...",
            setup="...",
            view="...",
        ),
        params=dict(...),
        remember=True
        )
    )
)

Flask-OAuthlib parameters

The Flask-OAuthlib parameters defines the remote application OAuth endpoints as well as the client id and secret. Full description of these parameters are given in the Flask-OAuthlib documentation.

Normally you will have to browse the remote application’s API documentation to find which URLs and scopes to use.

Below is an example for GitHub:

OAUTHCLIENT_REMOTE_APPS = dict(
    github=dict(
        # ...
        params=dict(
            request_token_params={'scope': 'user:email'},
            base_url='https://api.github.com/',
            request_token_url=None,
            access_token_url="https://github.com/login/oauth/access_token",
            access_token_method='POST',
            authorize_url="https://github.com/login/oauth/authorize",
            app_key="GITHUB_APP_CREDENTIALS",
        )
    )
)

GITHUB_APP_CREDENTIALS=dict(
    consumer_key="changeme"
    consumer_secret="changeme"
)

The app_key parameter allows you to put your sensitive client id and secret in your instance configuration (var/invenio.base-instance/invenio.cfg).

Handlers

Handlers allow customizing oauthclient endpoints for each remote application:

  • Authorized endpoint: /oauth/authorized/<REMOTE APP>/.
  • Disconnect endpoint: /oauth/disconnect/<REMOTE APP>/.
  • Sign up endpoint: /oauth/login/<REMOTE APP>/.

By default only authorized and disconnect handlers are required, and Invenio provide default implementation that stores the access token in the user session as well as to the database if the user is authenticated:

OAUTHCLIENT_REMOTE_APPS = dict(
    myapp=dict(
        # ...
        authorized_handler="invenio_oauthclient.handlers"
                   ":authorized_default_handler",
        disconnect_handler="invenio_oauthclient.handlers"
                   ":disconnect_handler",
        )
        # ...
    )
)

If you want to provide sign in/up functionality using oauthclient, Invenio comes with a default handler that will try to find a matching local user for a given authorize request.

OAUTHCLIENT_REMOTE_APPS = dict(
    orcid=dict(
        # ...
        authorized_handler="invenio_oauthclient.handlers"
                   ":authorized_signup_handler",
        disconnect_handler="invenio_oauthclient.handlers"
                   ":disconnect_handler",
        )
        signup_handler=dict(
            info="invenio_oauthclient.contrib.orcid:account_info",
            setup="invenio_oauthclient.contrib.orcid:account_setup",
            view="invenio_oauthclient.handlers:signup_handler",
        ),
        # ...
    )
)

Custom remote application

Some OAuth services require a specific handling of OAuth requests. If the standard flask-oauthlib.client.OAuthRemoteApp does not support it, it is possible to replace the standard OAuthRemoteApp for all remote application by referring to the custom class with the configuration variable OAUTHCLIENT_REMOTE_APP or for only one remote application by setting remote_app in your remote application configuration.

class CustomOAuthRemoteApp(OAuthRemoteApp):
    pass

app.config.update(
    OAUTHCLIENT_REMOTE_APP=
        'myproject.mymodule:CustomOAuthRemoteApp'
)

# OR

app.config.update(
    OAUTHCLIENT_REMOTE_APPS=dict(
        custom_app=dict(
            # ...
            remote_app=
                'myproject.mymodule:CustomOAuthRemoteApp'
        )
    )
)