OAuthClient¶
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:
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 becode
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.
The resource server asks the user to sign-in (if not already signed in).
The resource server asks the resource owner to authorize or reject the client’s request for access.
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.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¶
Edit your configuration. Ensure
invenio.modules.oauthclient
is included inPACKAGES
(by default it’s included).Define remote resource serves in the
OAUTHCLIENT_REMOTE_APPS
.PACKAGES = [ # ... 'invenio.modules.oauthclient', # ... ] OAUTHCLIENT_REMOTE_APPS = dict( # ... )
See Configuration for how to define remote applications in
OAUTHCLIENT_REMOTE_APPS
.
Configuration¶
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. |
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.modules.oauthclient.handlers"
":authorized_default_handler",
disconnect_handler="invenio.modules.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.modules.oauthclient.handlers"
":authorized_signup_handler",
disconnect_handler="invenio.modules.oauthclient.handlers"
":disconnect_handler",
)
signup_handler=dict(
info="invenio.modules.oauthclient.contrib.orcid:account_info",
setup="invenio.modules.oauthclient.contrib.orcid:account_setup",
view="invenio.modules.oauthclient.handlers:signup_handler",
),
# ...
)
)
Contrib¶
GitHub¶
Pre-configured remote application for enabling sign in/up with GitHub.
Usage:
Ensure you have
github3.py
package installed:cdvirtualenv src/invenio pip install -e .[github]
Edit your configuration and add:
from invenio.modules.oauthclient.contrib import github OAUTHCLIENT_REMOTE_APPS = dict( github=github.REMOTE_APP, ) GITHUB_APP_CREDENTIALS = dict( consumer_key="changeme", consumer_secret="changeme", )
Go to GitHub and register a new application: https://github.com/settings/applications/new. When registering the application ensure that the Authorization callback URL points to:
CFG_SITE_SECURE_URL/oauth/authorized/github/
(e.g.http://localhost:4000/oauth/authorized/github/
for development).Grab the Client ID and Client Secret after registering the application and add them to your instance configuration (
invenio.cfg
):GITHUB_APP_CREDENTIALS = dict( consumer_key="<CLIENT ID>", consumer_secret="<CLIENT SECRET>", )
Now go to
CFG_SITE_SECURE_URL/oauth/login/github/
(e.g. http://localhost:4000/oauth/login/github/)Also, you should see GitHub listed under Linked accounts: http://localhost:4000//account/settings/linkedaccounts/
By default the GitHub module will try first look if a link already exists between a GitHub account and a user. If no link is found, the module tries to retrieve the user email address from GitHub to match it with a local user. If this fails, the user is asked to provide an email address to sign-up.
In templates you can add a sign in/up link:
<a href="{{url_for('oauthclient.login', remote_app='github')}}">Sign in with GitHub</a>
ORCID¶
Pre-configured remote application for enabling sign in/up with ORCID.
Usage:
Edit your configuration and add:
from invenio.modules.oauthclient.contrib import orcid OAUTHCLIENT_REMOTE_APPS = dict( orcid=orcid.REMOTE_APP, ) ORCID_APP_CREDENTIALS = dict( consumer_key="changeme", consumer_secret="changeme", )
Note, if you want to use the ORCID sandbox, useorcid.REMOTE_SANDBOX_APP
instead oforcid.REMOTE_APP
.
Register a new application with ORCID. When registering the application ensure that the Redirect URI points to:
CFG_SITE_SECURE_URL/oauth/authorized/orcid/
(note, ORCID does not allow localhost to be used, thus testing on development machines is somewhat complicated by this).Grab the Client ID and Client Secret after registering the application and add them to your instance configuration (
invenio.cfg
):ORCID_APP_CREDENTIALS = dict( consumer_key="<CLIENT ID>", consumer_secret="<CLIENT SECRET>", )
Now go to
CFG_SITE_SECURE_URL/oauth/login/orcid/
(e.g. http://localhost:4000/oauth/login/orcid/)Also, you should see ORCID listed under Linked accounts: http://localhost:4000//account/settings/linkedaccounts/
By default the ORCID module will try first look if a link already exists between a ORCID account and a user. If no link is found, the user is asked to provide an email address to sign-up.
In templates you can add a sign in/up link:
<a href="{{url_for('oauthclient.login', remote_app='orcid')}}">
Sign in with ORCID
</a>
API¶
Handlers¶
Handlers for customizing oauthclient endpoints.
Store access token in session.
Default authorized handler.
Handle sign-in/up functionality.
-
invenio.modules.oauthclient.handlers.
disconnect_handler
(remote, *args, **kwargs)¶ Handle unlinking of remote account.
This default handler will just delete the remote account link. You may wish to extend this module to perform clean-up in the remote service before removing the link (e.g. removing install webhooks).
-
invenio.modules.oauthclient.handlers.
get_session_next_url
(remote_app)¶ Return redirect url stored in session.
-
invenio.modules.oauthclient.handlers.
make_handler
(f, remote, with_response=True)¶ Make a handler for authorized and disconnect callbacks.
Parameters: f – Callable or an import path to a callable
-
invenio.modules.oauthclient.handlers.
make_token_getter
(remote)¶ Make a token getter for a remote application.
-
invenio.modules.oauthclient.handlers.
oauth1_token_setter
(remote, resp, token_type='', extra_data=None)¶ Set an OAuth1 token.
-
invenio.modules.oauthclient.handlers.
oauth2_handle_error
(remote, resp, error_code, error_uri, error_description)¶ Handle errors during exchange of one-time code for an access tokens.
-
invenio.modules.oauthclient.handlers.
oauth2_token_setter
(remote, resp, token_type='', extra_data=None)¶ Set an OAuth2 token.
The refresh_token can be used to obtain a new access_token after the old one is expired. It is saved in the database for long term use. A refresh_token will be present only if access_type=offline is included in the authorization code request.
-
invenio.modules.oauthclient.handlers.
oauth_error_handler
(f)¶ Decorator to handle exceptions.
-
invenio.modules.oauthclient.handlers.
oauth_logout_handler
(sender_app, user=None)¶ Remove all access tokens from session on logout.
-
invenio.modules.oauthclient.handlers.
response_token_setter
(remote, resp)¶ Extract token from response and set it for the user.
-
invenio.modules.oauthclient.handlers.
set_session_next_url
(remote_app, url)¶ Store redirect url in session for security reasons.
-
invenio.modules.oauthclient.handlers.
signup_handler
(remote, *args, **kwargs)¶ Handle extra signup information.
-
invenio.modules.oauthclient.handlers.
token_delete
(remote, token='')¶ Remove OAuth access tokens from session.
-
invenio.modules.oauthclient.handlers.
token_getter
(remote, token='')¶ Retrieve OAuth access token.
Used by flask-oauthlib to get the access token when making requests.
Parameters: token – Type of token to get. Data passed from oauth.request()
to identify which token to retrieve.
-
invenio.modules.oauthclient.handlers.
token_session_key
(remote_app)¶ Generate a session key used to store the token for a remote app.
-
invenio.modules.oauthclient.handlers.
token_setter
(remote, token, secret='', token_type='', extra_data=None)¶ Set token for user.
Models¶
Models for storing access tokens and links between users and remote apps.
-
class
invenio.modules.oauthclient.models.
RemoteAccount
(**kwargs)¶ Storage for remote linked accounts.
-
client_id
¶ Client ID of remote application (defined in OAUTHCLIENT_REMOTE_APPS).
-
classmethod
create
(user_id, client_id, extra_data)¶ Create new remote account for user.
Parameters: - user_id – User id.
- client_id – Client id.
- extra_data – JSON-serializable dictionary of any extra data that needs to be save together with this link.
-
delete
()¶ Delete remote account together with all stored tokens.
-
extra_data
¶ Extra data associated with this linked account.
-
classmethod
get
(user_id, client_id)¶ Get RemoteAccount object for user.
Parameters: - user_id – User id
- client_id – Client id.
-
id
¶ Primary key.
-
tokens
¶ SQLAlchemy relationship to RemoteToken objects.
-
user
¶ SQLAlchemy relationship to user.
-
user_id
¶ Local user linked with a remote app via the access token.
-
-
class
invenio.modules.oauthclient.models.
RemoteToken
(**kwargs)¶ Storage for the access tokens for linked accounts.
-
access_token
¶ Access token to remote application.
-
classmethod
create
(user_id, client_id, token, secret, token_type='', extra_data=None)¶ Create a new access token.
Creates RemoteAccount as well if it does not exists.
-
classmethod
get
(user_id, client_id, token_type='', access_token=None)¶ Get RemoteToken for user.
-
classmethod
get_by_token
(client_id, access_token, token_type='')¶ Get RemoteAccount object for token.
-
id_remote_account
¶ Foreign key to account.
-
secret
¶ Used only by OAuth 1.
-
token
()¶ Get token as expected by Flask-OAuthlib.
-
token_type
¶ Type of token.
-
update_token
(token, secret)¶ Update token with new values.
-
Views¶
Client blueprint used to handle OAuth callbacks.
Authorized handler callback.
-
invenio.modules.oauthclient.views.client.
disconnect
(remote_app)¶ Disconnect user from remote application.
Removes application as well as associated information.
-
invenio.modules.oauthclient.views.client.
login
(remote_app)¶ Send user to remote application for authentication.
-
invenio.modules.oauthclient.views.client.
setup_app
()¶ Setup OAuth clients.
-
invenio.modules.oauthclient.views.client.
signup
(remote_app)¶ Extra signup step.
Account settings blueprint for oauthclient.
-
invenio.modules.oauthclient.views.settings.
index
(*args, **kwargs)¶ List linked accounts.
Forms¶
Forms for module.
Utils¶
Utility methods to help find, authenticate or register a remote user.
-
invenio.modules.oauthclient.utils.
oauth_authenticate
(client_id, userinfo, require_existing_link=False, remember=False)¶ Authenticate an oauth authorized callback.
-
invenio.modules.oauthclient.utils.
oauth_get_user
(client_id, account_info=None, access_token=None)¶ Retrieve user object for the given request.
Uses either the access token or extracted account information to retrieve the user object.
-
invenio.modules.oauthclient.utils.
oauth_link_external_id
(user, external_id=None)¶ Link a user to an external id.
-
invenio.modules.oauthclient.utils.
oauth_register
(account_info, form_data=None)¶ Register user if possible.
-
invenio.modules.oauthclient.utils.
oauth_unlink_external_id
(external_id)¶ Unlink a user from an external id.
Registries¶
Registries of handlers for oauthclient.
-
invenio.modules.oauthclient.client.
account_handlers
= <flask_registry.registries.core.DictRegistry object>¶ Registry of handlers for initializing an account.
-
invenio.modules.oauthclient.client.
disconnect_handlers
= <flask_registry.registries.core.DictRegistry object>¶ Registry of handlers for authorized handler callbacks.
-
invenio.modules.oauthclient.client.
handlers
= <flask_registry.registries.core.DictRegistry object>¶ Registry of handlers for authorized handler callbacks.
-
invenio.modules.oauthclient.client.
oauth
= <flask_oauthlib.client.OAuth object>¶ Flask-OAuthlib extension.
-
invenio.modules.oauthclient.client.
signup_handlers
= <flask_registry.registries.core.DictRegistry object>¶ Registry of handlers for signup handlers.
Errors¶
Module level errors.
-
exception
invenio.modules.oauthclient.errors.
OAuthClientError
(message, remote, response)¶ Define OAuth client exception.
Client errors happens when the client (i.e. Invenio) creates an invalid request.
-
exception
invenio.modules.oauthclient.errors.
OAuthError
(message, remote)¶ Base class for OAuth exceptions.
-
exception
invenio.modules.oauthclient.errors.
OAuthRejectedRequestError
(message, remote, response)¶ Define exception of rejected response during OAuth process.
-
exception
invenio.modules.oauthclient.errors.
OAuthResponseError
(message, remote, response)¶ Define response exception during OAuth process.