Metadata-Version: 1.1
Name: oauthsub
Version: 0.1.3
Summary: Simple oauth2 subrequest handler for nginx
Home-page: https://github.com/cheshirekow/oauthsub
Author: Josh Bialkowski
Author-email: josh.bialkowski@gmail.com
License: UNKNOWN
Download-URL: https://github.com/cheshirekow/oauthsub/archive/0.1.3.tar.gz
Description: ========
        oauthsub
        ========
        
        .. image:: https://travis-ci.com/cheshirekow/oauthsub.svg?branch=master
            :target: https://travis-ci.com/cheshirekow/oauthsub
        
        .. image:: https://readthedocs.org/projects/oauthsub/badge/
            :target: https://oauthsub.readthedocs.io
        
        Simple oauth2 subrequest handler for nginx
        
        A simple authentication service which can authenticate google/gsuite users
        using oauth2. The service is intended to provide the interface required by
        ``nginx`` ``mod_auth``, serving subrequests that authenticate/authorize users.
        Nothing in the design is specific to nginx, however, and it can likely be used
        with other reverse proxy configurations (such as apache).
        
        ``oauthsub`` is a flask application with the following routes:
        
            * ``<route_prefix>/login``: start of oauth dance
            * ``<route_prefix>/callback``: oauth redirect handler
            * ``<route_prefix>/logout``: clears user session
            * ``<route_prefix>/query_auth``: nginx subrequest handler
            * ``<route_prefix>/forbidden``: nginx subrequest handler
        
        where ``<route_prefix>`` is a configuration option (default ``/auth``).
        
        ``oauthsub`` uses the flask session interface. You can configure the session
        backend however you like (see configuration options). If you share the session
        key between ``oauthsub`` and another flask application behind the same nginx
        instance then you can access the ``oauthsub`` session variables directly
        (including google credentials object). Otherwise ``oauthsub`` can forward the
        username through an HTTP request header.
        
        ------------
        Installation
        ------------
        
        Install through pip with::
        
          pip install oauthsub
        
        or::
        
          pip install --user oauthsub
        
        -----
        Usage
        -----
        
        ::
        
            usage: oauthsub [-h] [--dump-config] [-v] [-l {debug,info,warning,error}]
                            [-c CONFIG_FILE] [-s {flask,gevent,twisted}]
                            [--rooturl ROOTURL] [--flask-debug [FLASK_DEBUG]]
                            [--response-header RESPONSE_HEADER]
                            [--allowed-domains [ALLOWED_DOMAINS [ALLOWED_DOMAINS ...]]]
                            [--host HOST] [--port PORT] [--logdir LOGDIR]
                            [--route-prefix ROUTE_PREFIX]
                            [--session-key-prefix SESSION_KEY_PREFIX]
                            [--bypass-key BYPASS_KEY] [--custom-template CUSTOM_TEMPLATE]
                            [--enable-forbidden [ENABLE_FORBIDDEN]]
        
            This lightweight web service performs authentication. All requests that reach
            this service should be proxied through nginx. See:
            https://developers.google.com/api-client-library/python/auth/web-app
        
            optional arguments:
              -h, --help            show this help message and exit
              --dump-config         Dump configuration and exit
              -v, --version         show program's version number and exit
              -l {debug,info,warning,error}, --log-level {debug,info,warning,error}
                                    Increase log level to include info/debug
              -c CONFIG_FILE, --config-file CONFIG_FILE
                                    use a configuration file
              -s {flask,gevent,twisted}, --server {flask,gevent,twisted}
                                    Which WGSI server to use
              --rooturl ROOTURL     The root URL for browser redirects
              --flask-debug [FLASK_DEBUG]
                                    Enable flask debugging for testing
              --response-header RESPONSE_HEADER
                                    If specified, the authenticated user's ``username``
                                    will be passed as a response header with this key.
              --allowed-domains [ALLOWED_DOMAINS [ALLOWED_DOMAINS ...]]
                                    List of domains that we allow in the `hd` field of
                                    thegoogle response. Set this to your company gsuite
                                    domains.
              --host HOST           The address to listening on
              --port PORT           The port to listen on
              --logdir LOGDIR       Directory where we store resource files
              --route-prefix ROUTE_PREFIX
                                    All flask routes (endpoints) are prefixed with this
              --session-key-prefix SESSION_KEY_PREFIX
                                    All session keys are prefixed with this
              --bypass-key BYPASS_KEY
                                    Secret string which can be used to bypass
                                    authorization if provided in an HTTP header
                                    `X-OAuthSub-Bypass`
              --custom-template CUSTOM_TEMPLATE
                                    Path to custom jinja template
              --enable-forbidden [ENABLE_FORBIDDEN]
                                    If true, enables the /forbidden endpoint, to which you
                                    can redirect 401 errors from your reverse proxy. This
                                    page is a simple message with active template but
                                    includes login links that will redirect back to the
                                    forbidden page after a successful auth.
        
        ``oauthsub`` is configurable through a configuration file in python (the file
        is ``exec``ed). Each configuration variable can also be specified on the
        command line (use ``oauthsub --help`` to see a list of options). If you'd
        like to dump a configuration file containing default values use::
        
            oauthsub --dump-config
        
        Which outputs something like::
        
            # The root URL for browser redirects
            rooturl = 'http://localhost'
        
            # Enable flask debugging for testing
            flask_debug = False
        
            # Secret key used to sign cookies
            flask_privkey = b'Kjla6e0hOIaoa4S22q/khlH2bP+nbdvt'
        
            # If specified, the authenticated user's ``username`` will be passed as a
            # response header with this key.
            response_header = None
        
            # List of domains that we allow in the `hd` field of thegoogle response. Set
            # this to your company gsuite domains.
            allowed_domains = ['gmail.com']
        
            # The address to listening on
            host = '0.0.0.0'
        
            # The port to listen on
            port = 8081
        
            # Directory where we store resource files
            logdir = '/tmp/oauthsub/logs'
        
            # Flask configuration options. Set session config here.
            flaskopt = {
              "SESSION_TYPE": "filesystem",
              "SESSION_FILE_DIR": "/tmp/oauthsub/session_data",
              "PERMANENT_SESSION_LIFETIME": 864000
            }
        
            # All flask routes (endpoints) are prefixed with this
            route_prefix = '/auth'
        
            # All session keys are prefixed with this
            session_key_prefix = 'oauthsub-'
        
            # Secret string which can be used to bypass authorization if provided in an HTTP
            # header `X-OAuthSub-Bypass`
            bypass_key = None
        
            # Dictionary mapping oauth privider names to the client secrets for that
            # provider.
            client_secrets = {}
        
            # Path to custom jinja template
            custom_template = None
        
            # If true, enables the /forbidden endpoint, to which you can redirect 401 errors
            # from your reverse proxy. This page is a simple message  with active template
            # but includes login links that will redirect back to the forbidden page after a
            # successful auth.
            enable_forbidden = True
        
        
        -----------
        Basic setup
        -----------
        
        The nginx server will serve anything under ``public`` or ``auth`` without
        authentication or authorization. For any other request, nginx will forward
        the http headers to the authentication service over http. The authentication
        service will return an HTTP status code of 200 if the user is
        authenticated/authorized, and 401 if they are not. All users with who login
        with an account that is within the authorized domain list is authorized.
        
        The nginx server proxies all requests rooted at ``auth/`` to the authentication
        service which is a python flask application. The auth service uses a session
        (persisted through a cookie) to store the user's authenticated credentials
        (email address reported by google). If the user is not authenticated or
        is not authorized, the 401 error page is served by the authentication service
        to provide some info about why the request was denied (i.e. what they are
        currently logged in as). There is also a link on that page to login if they are
        not.
        
        ----------------
        Configure Google
        ----------------
        
        Go to the Google `Developer Dashboard`_ and create a new project. Select the
        project in the top left next to the GoogleAPIs logo. Click "Credentials" under
        the menu on the left of the screen. Click "Create credentials" to get the
        client_secret.json file. Then click "OAuth consent screen" and fill out the
        info, upload a logo, etc.
        
        Add authorized domains:
        
          * For testing
        
            * http://lvh.me:8080/
            * http://lvh.me:8081/
            * https://lvh.me:8443/
        
        And Authorized redirects:
        
          * For testing:
        
            * http://lvh.me:8080/auth/callback?provider=google
            * http://lvh.me:8081/auth/callback?provider=google
            * https://lvh.me:8443/auth/callback?provider=google
        
          * For deployment:
        
            * https://server.yoursite.com/auth/callback
        
        NOTE(josh): As of January 2019 Google has recently changed their developer
        settings and requirements for OAUTH access. They used to allow `localhost` and
        now they do not. An alternative is to use `lvh.me` which currently resolves
        through DNS to 127.0.0.1. Be careful, however, as this is a common solution
        cited on the interwebs but no one seems to know who controls this domain and
        they may be nefarious actors.
        
        .. _`Developer Dashboard`: https://console.developers.google.com/apis/credentials
        
        
        ----------------
        Configure Github
        ----------------
        
        Go to the Github `Developer Settings`_. Click "New OAuth App". Copy down the
        "Client ID" and "Client Secret" and add them to your ``config.py``. Set the
        "Authorization Callback URL" to::
        
            lvh.me:8080/auth/callback
        
        for testing, or your real server for deployment.
        
        .. _`Developer Settings`: https://github.com/settings/developers
        
        
        ---------------
        Configure nginx
        ---------------
        
        ::
        
            location / {
              # Use ngx_http_auth_request_module to auth the user, sending the
              # request to the /auth/query_auth URI which will return an http
              # error code of 200 if approved or 401 if denied.
              auth_request /auth/query_auth;
        
              # First attempt to serve request as file, then
              # as directory, then fall back to displaying a 404.
              try_files $uri $uri/ =404;
            }
        
            # Whether we have one or not, browsers are going to ask for this so we
            # probably shouldn't plumb it through auth.
            location = /favicon.ico {
              auth_request off;
              try_files $uri $uri/ =404;
            }
        
            # The authentication service exposes a few other endpoints, all starting
            # with the uri prefix /auth. These endpoints are for the oauth2 login page,
            # callback, logout, etc
            location /auth {
              auth_request off;
              proxy_pass http://localhost:8081;
              proxy_pass_request_body on;
              proxy_set_header X-Original-URI $request_uri;
            }
        
            # the /auth/query URI is proxied to the authentication service, which will
            # return an http code 200 if the user is authorized, or 401 if they are
            # not
            location = /auth/query_auth {
              proxy_pass http://localhost:8081;
              proxy_pass_request_body off;
              proxy_set_header Content-Length "";
              proxy_set_header X-Original-URI $request_uri;
              proxy_pass_header X-OAuthSub-Bypass-Key;
              proxy_pass_header X-OAuthSub-Bypass-User;
            }
        
            # if the server is using letsencrypt  certbot then we'll want this
            # directory to be accessible publicly
            location /.well-known {
              auth_request off;
            }
        
            # we may want to keep some uri's available without authentication
            location /public {
              auth_request off;
            }
        
            # for 401 (not authorized) redirect to the auth service which will include
            # the original URI in it's oauthflow and redirect back to the originally
            # requested page after auth
            error_page 401 /auth/forbidden;
        
        If you want ``oauthsub`` to forward the username through a header variable then
        set the ``request_header`` configuration variable and add the following to your
        `nginx`_ configuration. In this example the ``request_header`` is ``X-User``
        and ``nginx`` is reverse-proxying a second service listening on 8085.::
        
            location / {
                auth_request      /auth/query_auth;
                auth_request_set $user $upstream_x_user;
                proxy_set_header x-user $user;
                proxy_pass       http://localhost:8082;
            }
        
        .. _`nginx`: https://www.nginx.com/resources/admin-guide/restricting-access-auth-request/
        
        -------------------
        Add a systemd unit
        -------------------
        
        For linux servers using systemd, you can add
        ``/etc/systemd/system/oauthsub.service``, an example which is given below
        assuming we want the service to run as user ``ubuntu`` and the configuration
        file is in ``/etc/oauthsub.py``.
        
        ::
        
            [Unit]
            Description=oauthsub service
            After=nginx.service
        
            [Service]
            Type=simple
            ExecStart=/usr/local/bin/oauthsub -c /etc/oauthsub.py
            User=ubuntu
            Restart=on-abort
        
            [Install]
            WantedBy=multi-user.target
        
        -------------------
        Testing the service
        -------------------
        
        Test the service directly on localhost, you can use the default configuration
        but point to a config file with your ``client_secrets.json``
        (assuming you've enabled ``http://lvh.me:8081/auth/callback`` as an authorized
        redirect on google)::
        
            oauthsub --flask-debug \
                       --secrets /tmp/client_secrets.json
        
        And then navigate to ``http://localhost:8081/auth`` from your browser.
        
        To test the service behind nginx on localhost, with nginx running on port 8081
        (again assuming you've enabled ``http://localhost:8081/auth/callback`` as an
        authorized redirect on google). Save this file as ``/tmp/nginx.conf``::
        
            daemon off;
            worker_processes auto;
            pid /tmp/nginx.pid;
        
            events {
              worker_connections 768;
            }
        
            http {
              sendfile on;
              tcp_nopush on;
              tcp_nodelay on;
              keepalive_timeout 65;
              types_hash_max_size 2048;
              include /etc/nginx/mime.types;
              default_type application/octet-stream;
              ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
              ssl_prefer_server_ciphers on;
              access_log /tmp/nginx-access.log;
              error_log /tmp/nginx-error.log;
              gzip on;
              gzip_disable "msie6";
        
              server {
        
                listen 8080 default_server;
                listen [::]:8080 default_server;
        
                index index.html index.htm index.nginx-debian.html;
                server_name cheshiresoft;
                root /tmp/webroot;
        
                location / {
                  auth_request /auth/query_auth;
                  try_files $uri $uri/ =404;
                }
        
                location = /favicon.ico {
                  auth_request off;
                  try_files $uri $uri/ =404;
                }
        
                location /auth {
                  auth_request off;
                  proxy_pass http://localhost:8081;
                  proxy_pass_request_body on;
                  proxy_set_header X-Original-URI $request_uri;
                }
        
                location = /auth/query_auth {
                  proxy_pass http://localhost:8081;
                  proxy_pass_request_body off;
                  proxy_set_header Content-Length "";
                  proxy_set_header X-Original-URI $request_uri;
                  proxy_pass_header X-OAuthSub-Bypass-Key;
                  proxy_pass_header X-OAuthSub-Bypass-User;
                }
        
                location /public {
                  auth_request off;
                }
        
                error_page 401 /auth/forbidden;
              }
            }
        
        Start simple auth with::
        
            oauthsub --flask-debug \
                       --config /tmp/config.py \
                       --port 8081 \
                       --rooturl http://localhost:8080
        
        Start nginx with::
        
            nginx -c /tmp/nginx.conf -g "error_log /tmp/nginx-error.log;"
        
        And navigate to "http://localhost:8080/" with your browser. You should be
        initially denied, required to login, and then directed to the default
        "welcome to nginx" page (unless you've written something else to your
        default webroot).
        
Keywords: oauth,nginx,flask
Platform: UNKNOWN
