Using mwoauth2 in a non-Flask tool

To use mwoauth2 in a non-Flask tool, you will first have to implement an interface to the session store of whichever web framework you’re using, by providing six accessor functions: get_oauth_state, set_oauth_state, and pop_oauth_state to get, set and pop the OAuth state, and get_access_token, set_access_token, and pop_access_token do the same for the access token. For instance, if your session store has a dict-like interface, these functions could look like this:

def get_oauth_state(session):
    return session.get('oauth_state')

def set_oauth_state(state, session):
    session['oauth_state'] = state

def pop_oauth_state(session):
    return session.pop('oauth_state')

def get_access_token(session):
    return session.get('access_token')

def set_access_token(token, session):
    session['access_token'] = token

def pop_access_token(session):
    return session.pop('access_token')

And you would call mwoauth2 methods with a session argument, which would be passed through to the accessor functions, for instance:

url = oauth.authorization_url(session)

If you need more parameters (or fewer) to access the session store, that works too: any number of args (and kwargs) are passed through from the mwoauth2 methods to the accessor functions. Just make sure that all six functions are consistent in the parameters they take (plus the extra state/token parameter of the set_* functions) – if you use a type checker, this will be checked for you automatically.

Note that the session store needs to be accessible whenever mwoauth2 is used, not just while the user logs in: OAuth 2 access tokens must be refreshed regularly (every 4 hours in Wikimedia production), and in order to handle this automatically, mwoauth2 needs to be able to call set_access_token whenever you make an OAuth-authenticated request.

Once the session store accessors are implemented, you can create your mwoauth2 instance, generally at tool initialization time (the same instance will be reused between requests):

oauth = MWOAuth2(
    host='https://meta.wikimedia.org',  # the wiki where OAuth authorization will happen
    client_id=...,  # 32 hexadecimal characters, from some secret config source
    client_secret=...,   # 40 hexadecimal characters, ditto
    get_oauth_state=get_oauth_state,  # or whatever you called the function
    set_oauth_state=set_oauth_state,
    pop_oauth_state=pop_oauth_state,
    get_access_token=get_access_token,
    set_access_token=set_access_token,
    pop_access_token=pop_access_token,
    user_agent=...,
)

If you use the mwapi library, use the MWOAuth2MWApi class instead of MWOAuth2; the constructor parameters are the same. The client ID and secret were generated for you when you registered the OAuth 2 client; you should store them in some sort of confidential configuration (e.g. environment variables) and load them from there when initializing the tool. The user_agent string must conform with the policy; Toolforge tools can get a suitable string from toolforge.set_user_agent():

user_agent = toolforge.set_user_agent('example-tool', email='tool@example.com')
# or
user_agent = 'example-tool (tool@example.com) other-details...'

When targeting non-Wikimedia wikis, you may have to specify a custom api_path= parameter if it is not /w/api.php.

Now the oauth instance is ready to use. To check if the user is logged in, use has_access_token():

def some_route_handler():
    if oauth.has_access_token(session):  # or whatever parameters your accessor functions take
        # user is logged in
        ...
    else:
        # logged out, show a login link
        ...

To log in, redirect the user to the authorization_url():

def some_login_handler():
    url = oauth.authorization_url(session)  # or whatever parameters your accessor functions take
    return some_redirect_to(url)

In the OAuth callback (the URL of which you configured in our OAuth client registration), call fetch_token() to finish logging in:

def some_oauth_callback():
    request_url = ...  # get it from your web framework, somehow
    oauth.fetch_token(request_url, session)  # or whatever parameters your accessor functions take
    # ...probably redirect back to the index page

Now the user is logged in and OAuth is ready to use. If you use mwapi, use mwapi_session():

def get_userinfo_json():
    session = oauth.mwapi_session(session)  # or whatever parameters your accessor functions take
    if session is None:
        return 'not logged in'
    response = session.get(action='query', meta='userinfo')
    return response['query']['userinfo']

Otherwise, use oauth2_session():

def get_userinfo_json():
    session = oauth.oauth2_session(session)  # or whatever parameters your accessor functions take
    if session is None:
        return 'not logged in'
    response = session.get('https://meta.wikimedia.org/w/api.php',
                           params={'action': 'query', 'meta': 'userinfo', 'format': 'json'})
    return response.json()['query']['userinfo']

To log the user out, call pop_access_token():

def some_logout_handler():
    try:
        oauth.pop_access_token(session)  # or whatever parameters your accessor functions take
    except KeyError:
        pass
    # ...probably redirect back to the index page

If you’ve successfully used mwoauth2 with a non-Flask framework, feel free to open a Phabricator task or create a merge request to add those session store accessor functions to the library itself.