├── userservice ├── oauth │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── tests.py │ ├── admin.py │ ├── models.py │ ├── apps.py │ ├── forms.py │ ├── templates │ │ └── oauth │ │ │ ├── oauth.html │ │ │ ├── applications.html │ │ │ └── base.html │ ├── views.py │ └── kong.py ├── userservice │ ├── __init__.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── requirements.txt ├── db.sqlite3 ├── Dockerfile └── manage.py ├── client ├── requirements.txt ├── Dockerfile ├── index.html ├── app.py └── templates │ └── index.html ├── .gitignore ├── service1 ├── Dockerfile ├── app.js └── package.json ├── service2 ├── Dockerfile ├── app.js └── package.json ├── register2.sh ├── environment.env ├── register.sh ├── LICENSE ├── docker-compose.yml └── README.md /userservice/oauth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /userservice/userservice/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /userservice/oauth/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | requests 3 | -------------------------------------------------------------------------------- /userservice/requirements.txt: -------------------------------------------------------------------------------- 1 | django>=1.9 2 | requests 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | __pycache__ 3 | .DS_Store 4 | *.pyc 5 | -------------------------------------------------------------------------------- /userservice/oauth/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /userservice/oauth/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /service1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:5 2 | RUN mkdir /code 3 | WORKDIR /code 4 | ADD . /code/ 5 | #RUN npm install 6 | -------------------------------------------------------------------------------- /userservice/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toast38coza/docker-kong-oauth/HEAD/userservice/db.sqlite3 -------------------------------------------------------------------------------- /service2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:5 2 | RUN mkdir /code 3 | WORKDIR /code 4 | ADD . /code/ 5 | #RUN npm install express 6 | -------------------------------------------------------------------------------- /userservice/oauth/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | -------------------------------------------------------------------------------- /userservice/oauth/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class AuthConfig(AppConfig): 7 | name = 'auth' 8 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5 2 | ENV PYTHONUNBUFFERED 1 3 | RUN mkdir /code 4 | WORKDIR /code 5 | ADD requirements.txt /code/ 6 | RUN pip install -r requirements.txt 7 | ADD . /code/ 8 | -------------------------------------------------------------------------------- /register2.sh: -------------------------------------------------------------------------------- 1 | curl -X POST http://docker.local:8001/apis/$1/plugins \ 2 | --data "name=oauth2" 3 | curl -X POST http://docker.local:8001/apis/$2/plugins \ 4 | --data "name=oauth2" 5 | -------------------------------------------------------------------------------- /userservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5 2 | ENV PYTHONUNBUFFERED 1 3 | RUN mkdir /code 4 | WORKDIR /code 5 | ADD requirements.txt /code/ 6 | RUN pip install -r requirements.txt 7 | ADD . /code/ 8 | -------------------------------------------------------------------------------- /environment.env: -------------------------------------------------------------------------------- 1 | client_id=63072dd44bee4403a1b0c69564c7ebed 2 | client_secret=9a49af96afac49b78319ce0a18e6afc5 3 | kong_url=https://192.168.99.100:8443 4 | kong_admin_url=http://192.168.99.100:8001 5 | -------------------------------------------------------------------------------- /service2/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.get('/', function (req, res) { 5 | res.send('Hello Something Else!'); 6 | }); 7 | 8 | app.listen(3000, function () { 9 | console.log('Example app listening on port 3000!'); 10 | }); 11 | -------------------------------------------------------------------------------- /service1/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.get('/', function (req, res) { 5 | console.log("here .."); 6 | res.send('Hello World!'); 7 | }); 8 | 9 | app.listen(3000, function () { 10 | console.log('Example app listening on port 3000! asdasds'); 11 | }); 12 | -------------------------------------------------------------------------------- /userservice/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "userservice.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /service1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "service1", 3 | "version": "1.0.0", 4 | "description": "A very random service", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Christo Crampton", 10 | "license": "MIT", 11 | "dependencies": { 12 | "express": "^4.13.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /service2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "service1", 3 | "version": "1.0.0", 4 | "description": "A very random service", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Christo Crampton", 10 | "license": "MIT", 11 | "dependencies": { 12 | "express": "^4.13.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userservice/userservice/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for userservice project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "userservice.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /register.sh: -------------------------------------------------------------------------------- 1 | curl -X POST \ 2 | --url http://$1:8001/apis/ \ 3 | --data "name=service1" \ 4 | --data "upstream_url=http://$1:8003" \ 5 | --data "request_host=service1.com" \ 6 | --data "request_path=/service1" \ 7 | --data "strip_request_path=true" 8 | 9 | curl -X POST \ 10 | --url http://$1:8001/apis/ \ 11 | --data "name=service2" \ 12 | --data "upstream_url=http://$1:8004" \ 13 | --data "request_host=service2.com" \ 14 | --data "request_path=/service2" \ 15 | --data "strip_request_path=true" 16 | -------------------------------------------------------------------------------- /userservice/oauth/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from oauth import kong 3 | 4 | class ClientApplicationForm(forms.Form): 5 | 6 | application_name = forms.CharField(label='Application name', max_length=100) 7 | redirect_uri = forms.CharField(label='Redirect', max_length=100) 8 | 9 | def save(self, consumer_id): 10 | 11 | data = self.cleaned_data 12 | 13 | app_name = data.get("application_name") 14 | redirect_uri = data.get("redirect_uri") 15 | return kong.create_client_application(consumer_id, app_name, redirect_uri) 16 | 17 | -------------------------------------------------------------------------------- /userservice/userservice/urls.py: -------------------------------------------------------------------------------- 1 | """userservice URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | from oauth import views 19 | 20 | urlpatterns = [ 21 | url(r'^oauth/', views.oauth_allow_access, name="oauth"), 22 | url(r'^authorize/', views.perform_oauth), 23 | url(r'^application/', views.create_application), 24 | url('^', include('django.contrib.auth.urls')), 25 | url(r'^admin/', admin.site.urls), 26 | ] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Christo Crampton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /userservice/oauth/templates/oauth/oauth.html: -------------------------------------------------------------------------------- 1 | {% extends "oauth/base.html" %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 |
7 |
8 |

Welcome: {{user.username}}

9 |
10 | 11 |
12 |
Would you like give the application {{client}} by {{consumer}} permissions to authenticate on your behalf?
13 | 14 | {% csrf_token %} 15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 |
30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Authenticate yo bad self 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 |

Hi there

21 |
22 |
23 |

24 | Client_id: {{client_id}}
25 | Client_secret: {{client_secret}}
26 |

27 |
28 |
29 | Authenticate 32 |
33 | 34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | kong-database: 5 | restart: on-failure 6 | image: cassandra:2.2.5 7 | container_name: kong-database 8 | ports: 9 | - "9042:9042" 10 | volumes: 11 | - "db-data:/var/lib/cassandra" 12 | kong: 13 | restart: on-failure 14 | image: kong:0.9 15 | container_name: kong 16 | ports: 17 | - "8000:8000" 18 | - "8443:8443" 19 | - "8001:8001" 20 | - "7946:7946" 21 | - "7946:7946/udp" 22 | links: 23 | - kong-database:kong-database 24 | environment: 25 | - DATABASE=cassandra 26 | security_opt: 27 | - label:seccomp:unconfined 28 | userservice: 29 | build: userservice 30 | env_file: environment.env 31 | command: python manage.py runserver 0.0.0.0:8000 32 | volumes: 33 | - './userservice:/code' 34 | ports: 35 | - '8002:8000' 36 | service1: 37 | build: service1 38 | command: node app.js 39 | volumes: 40 | - './service1:/code' 41 | ports: 42 | - '8003:3000' 43 | service2: 44 | build: service2 45 | command: node app.js 46 | volumes: 47 | - './service2:/code' 48 | ports: 49 | - '8004:3000' 50 | client: 51 | build: client 52 | command: python app.py 53 | env_file: environment.env 54 | volumes: 55 | - ./client:/code 56 | ports: 57 | - "80:5000" 58 | ui: 59 | restart: on-failure 60 | image: pgbi/kong-dashboard 61 | ports: 62 | - "8999:8080" 63 | 64 | volumes: 65 | db-data: 66 | -------------------------------------------------------------------------------- /userservice/oauth/templates/oauth/applications.html: -------------------------------------------------------------------------------- 1 | {% extends "oauth/base.html" %} 2 | 3 | {% block 'content' %} 4 | 5 |
6 | 7 | {% if consumer %} 8 |
9 |
10 |

Consumer

11 |
12 | 13 |
14 | {% for key, value in consumer.items %} 15 |
  • 16 |
    {{key}}: {{value}}
    17 |
  • 18 | {% endfor %} 19 |
    20 |
    21 | {% endif %} 22 |
    23 |
    24 |
    25 |
    26 |

    Create an application

    27 |
    28 |
    29 | 30 | {% csrf_token %} 31 | {{application_form}} 32 | 33 |
    34 |
    35 | 36 |
    37 |
    38 |
    39 | 40 | {% if client_list.data %} 41 |

    Clients

    42 | 43 | {% for client in client_list.data %} 44 |
    45 |
    46 |

    {{ client.name }}

    47 |
    48 | 49 |
    50 | {% for key, value in client.items %} 51 |
  • 52 |
    {{key}}: {{value}}
    53 |
  • 54 | {% endfor %} 55 |
    56 | 60 |
    61 | {% endfor %} 62 | {% endif %} 63 |
    64 | 65 | {% endblock %} -------------------------------------------------------------------------------- /client/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request 2 | import requests, os 3 | 4 | app = Flask(__name__) 5 | 6 | ## settings: 7 | client_id = os.environ.get('client_id', None) 8 | client_secret = os.environ.get('client_secret', None) 9 | kong_url = os.environ.get('kong_url', None) 10 | 11 | def get_token(code, client_id, client_secret): 12 | 13 | oauth_host = "service1.com" 14 | headers = { "Host": oauth_host } 15 | data = { 16 | "grant_type": "authorization_code", 17 | "client_id": client_id, 18 | "client_secret": client_secret, 19 | "code": code, 20 | } 21 | url = "{}/oauth2/token" . format (kong_url) 22 | return requests.post(url, data, headers=headers, verify=False) 23 | 24 | 25 | @app.route("/") 26 | def hello(): 27 | 28 | code = request.args.get('code', None) 29 | token = None 30 | service1_response = None 31 | service2_response = None 32 | if code is not None: 33 | # get token 34 | 35 | token = get_token(code, client_id, client_secret).json() 36 | print (token) 37 | url1 = "{}/service1?access_token={}" . format (kong_url, token.get('access_token')) 38 | url2 = "{}/service2?access_token={}" . format (kong_url, token.get('access_token')) 39 | service1_response = requests.get(url1, verify=False) 40 | service2_response = requests.get(url2, verify=False) 41 | # call service 1 42 | # call service 2 43 | 44 | context = { 45 | "client_id": client_id, 46 | "client_secret": client_secret, 47 | "code": code, 48 | "token_response": token, 49 | "service_response1": service1_response, 50 | "service_response2": service2_response 51 | 52 | } 53 | return render_template('index.html', **context) 54 | 55 | if __name__ == "__main__": 56 | app.run(host='0.0.0.0', debug=True) 57 | 58 | """ 59 | curl http://docker.local:8000/oauth2/token \ 60 | -H "Host: service1.com" \ 61 | -H "x-forwarded-proto: https" -d "grant_type=authorization_code" -d "client_id=b4b123e18f3349e6bc7172a656692612" -d "client_secret=10639ab4622147318f7910bcf7b7b460" -d "code=b1485adbf74745e8b39bf53b0fd76118" --insecure 62 | 63 | curl -X GET 192.168.99.100:8000/service1?access_token=dd9f9fbe63a749d2b2e10310ce992b14 64 | 65 | """ -------------------------------------------------------------------------------- /userservice/oauth/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.auth import authenticate, login 3 | from django.contrib.auth.decorators import login_required 4 | from django.views.decorators.http import require_http_methods 5 | from django.conf import settings 6 | from django.http import HttpResponseRedirect 7 | from oauth import kong, forms 8 | 9 | @login_required 10 | @require_http_methods(["GET"]) 11 | def oauth_allow_access(request): 12 | 13 | client_id = request.GET.get('client_id') 14 | client = kong.get_client(client_id) 15 | client_data = client.json().get('data')[0] 16 | 17 | consumer_id = client_data.get('consumer_id') 18 | consumer = kong.get_consumer(consumer_id) 19 | 20 | context = { 21 | "client": client_data.get("name"), 22 | "client_id": client_id, 23 | "consumer": consumer.json().get("username"), 24 | "user": request.user, 25 | } 26 | return render(request, 'oauth/oauth.html', context) 27 | 28 | @login_required 29 | @require_http_methods(["GET", "POST"]) 30 | def create_application(request): 31 | 32 | consumer = kong.get_or_create_consumer(request.user) 33 | 34 | if request.method == 'POST': 35 | application_form = forms.ClientApplicationForm(request.POST) 36 | if application_form.is_valid(): 37 | application_form.save(request.user) 38 | else: 39 | data = { 40 | "username": request.user.username, 41 | "custom_id": request.user.pk, 42 | "consumer": consumer, 43 | } 44 | application_form = forms.ClientApplicationForm(initial=data) 45 | 46 | if consumer is not None: 47 | client_list = kong.get_consumer_clients(consumer.json().get("id")) 48 | 49 | 50 | context = { 51 | "application_form": application_form, 52 | "consumer": consumer.json(), 53 | "client_list": client_list.json() 54 | } 55 | return render(request, 'oauth/applications.html', context) 56 | 57 | @require_http_methods(["POST"]) 58 | def perform_oauth(request): 59 | 60 | client_id = request.POST.get('client_id') 61 | user_id = request.POST.get('user_id') 62 | response = kong.get_access_code(client_id, user_id) 63 | 64 | print (response.content) 65 | 66 | redirect_url = response.json().get('redirect_uri') 67 | return HttpResponseRedirect(redirect_url) 68 | -------------------------------------------------------------------------------- /client/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Authenticate yo bad self 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 |
    16 | 17 |
    18 |
    19 |

    Hi there

    20 |
    21 |
    22 | {% if client_id %} 23 |

    24 | Your client_id: {{client_id}}
    25 | Your client_secret: {{client_secret}}
    26 |

    27 | {% else %} 28 | No Client details are set. 29 | {% endif %} 30 |
    31 | {% if client_id %} 32 |
    37 | {% else %} 38 |
    39 | 42 | Create a client application? 43 | {% endif %} 44 | 45 |
    46 | 47 | 48 | {% if token_response %} 49 |
    50 |
    51 |
    52 |

    Authentication happened!

    53 |
    54 |
    55 | 56 |

    Token Info:

    57 |
     {{token_response}}
    58 | {% if service_response1.content %} 59 |

    service_response1:

    60 |
     {{service_response1.content}}
    61 |

    service_response2:

    62 |
    {{service_response2.content}}
    63 | {% endif %} 64 | 65 |
    66 |
    67 | {% endif %} 68 |
    69 |
    70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /userservice/oauth/kong.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth.models import User 3 | import requests 4 | 5 | def create_consumer(user): 6 | """ 7 | curl -X POST http://docker.local:8001/consumers/ \ 8 | --data "username=user123" \ 9 | --data "custom_id=1" 10 | """ 11 | data = { 12 | "username": user.username, 13 | "custom_id": user.pk 14 | } 15 | url = "{}/consumers/" . format (settings.KONG_ADMIN_URL) 16 | return requests.post(url, data) 17 | 18 | def create_client_application(consumer_id, app_name, redirect_uri): 19 | """ 20 | curl -X POST http://docker.local:8001/consumers/26550d58-68f0-4f25-8ab0-4174998474e6/oauth2 \ 21 | --data "name=Test%20Application" \ 22 | --data "redirect_uri=http://docker.local" 23 | """ 24 | 25 | data = { 26 | "name": app_name, 27 | "redirect_uri": redirect_uri 28 | } 29 | url = "{}/consumers/{}/oauth2" . format (settings.KONG_ADMIN_URL, consumer_id) 30 | return requests.post(url, data) 31 | 32 | def get_consumer_clients(consumer_id): 33 | url = "{}/consumers/{}/oauth2" . format (settings.KONG_ADMIN_URL, consumer_id) 34 | return requests.get(url) 35 | 36 | def get_or_create_consumer(user): 37 | consumer_response = get_consumer_by_username(user.username) 38 | if consumer_response.status_code == 404: 39 | consumer_response = create_consumer(user) 40 | return consumer_response 41 | 42 | def get_consumer(consumer_id): 43 | url = "{}/consumers/{}" . format (settings.KONG_ADMIN_URL, consumer_id) 44 | return requests.get(url) 45 | 46 | def get_consumer_by_username(username): 47 | url = "{}/consumers/{}" . format (settings.KONG_ADMIN_URL, username) 48 | return requests.get(url) 49 | 50 | def get_client(client_id): 51 | url = "{}/oauth2?client_id={}" . format (settings.KONG_ADMIN_URL, client_id) 52 | return requests.get(url) 53 | 54 | def get_plugins(api_id): 55 | url = "{}/apis/{}/plugins" . format (settings.KONG_ADMIN_URL, api_id) 56 | return requests.get(url) 57 | 58 | def get_plugin(api_id, plugin_id): 59 | """ 60 | http://docker.local:8001/apis/baa777ec-ae40-46f3-98eb-e4ee00474743/plugins/e80d4743-26b5-4846-8dce-fbb0e393766c 61 | """ 62 | url = "{}/apis/{}/{}" . format (settings.KONG_ADMIN_URL, api_id, plugin_id) 63 | return requests.get(url) 64 | 65 | def get_access_code(client_id, user_id): 66 | """ 67 | curl http://docker.local:8000/oauth2/authorize \ 68 | --header "Host: service1.com" \ 69 | --header "x-forwarded-proto: https" \ 70 | --data "client_id=b4b123e18f3349e6bc7172a656692612" 71 | --data "response_type=code" 72 | --data "provision_key=7656a7f4dee345a6a1270a273c099480" 73 | --data "authenticated_userid=user123" 74 | """ 75 | 76 | oauth_host = settings.OAUTH_SERVICE.get("host") 77 | provision_key = settings.OAUTH_SERVICE.get("provision_key") 78 | 79 | url = "{}/oauth2/authorize" . format (settings.KONG_URL) 80 | headers = { "Host": oauth_host } 81 | data = { 82 | "client_id": client_id, 83 | "response_type": "code", 84 | "provision_key": provision_key, 85 | "authenticated_userid": user_id 86 | } 87 | return requests.post(url, data, headers=headers, verify=False) 88 | 89 | def get_token(code, client_id, client_secret): 90 | """ 91 | Given an access code, get the token 92 | --- 93 | curl http://docker.local:8000/oauth2/token \ 94 | -d "grant_type=authorization_code" -d "client_id=b4b123e18f3349e6bc7172a656692612" -d "client_secret=10639ab4622147318f7910bcf7b7b460" -d "code=b1485adbf74745e8b39bf53b0fd76118" --insecure 95 | 96 | """ 97 | oauth_host = settings.OAUTH_SERVICE.get("host") 98 | headers = { "Host": oauth_host } 99 | data = { 100 | "grant_type": "authorization_code", 101 | "client_id": client_id, 102 | "client_secret": client_secret, 103 | "code": code, 104 | } 105 | url = "{}/oauth2/token" . format (settings.KONG_URL) 106 | return requests.post(url, data, headers=headers, verify=False) 107 | -------------------------------------------------------------------------------- /userservice/oauth/templates/oauth/base.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Material Design Lite 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
    56 |
    57 |
    58 |
    59 |
    60 |

    Kong oauth

    61 |
    62 |
    63 |
    64 | 68 |
    69 |
    70 |
    71 | {% block 'content' %}{% endblock %} 72 |
    73 |
    74 |
    75 | View Source 76 | 77 | 78 | -------------------------------------------------------------------------------- /userservice/userservice/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for userservice project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'afv!@d!e*)uwta!x@lh@ywm+dvl5t!k+25dr*42_3x%j@mtos)' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | 'oauth', 42 | ] 43 | 44 | MIDDLEWARE_CLASSES = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'userservice.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'userservice.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | 'ENGINE': 'django.db.backends.sqlite3', 82 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 108 | 109 | LANGUAGE_CODE = 'en-us' 110 | 111 | TIME_ZONE = 'UTC' 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 122 | 123 | STATIC_URL = '/static/' 124 | 125 | KONG_ADMIN_URL = os.environ.get('kong_admin_url', 'http://192.168.99.100:8001') 126 | KONG_URL = os.environ.get('kong_url', 'https://192.168.99.100:8443') 127 | 128 | # we always authenticate against a specific service 129 | # a code/token from one service can be used against other services 130 | # using oauth2 plugin 131 | OAUTH_SERVICE = { 132 | "host": "service1.com", 133 | "provision_key": "3058d5b2d6c74fcba58f439951206c4b" 134 | } 135 | LOGIN_URL = '/admin/login/' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-kong-oauth 2 | An example of implementing Kong's oauth plugin with docker 3 | 4 | Check out the associated blog post: [Kong oAuth with a Django backend](http://blog.toast38coza.me/kong-oauth-with-a-django-backend-2/) 5 | 6 | ## Getting Started 7 | 8 | ``` 9 | docker-compose up -d && docker-compose logs 10 | ``` 11 | 12 | This will launch the following (assuming docker-machine ip is: `192.168.99.100`): 13 | 14 | * Kong: 192.168.99.100:8000 15 | * Kong Admin: 192.168.99.100:8001 16 | * UserService: 192.168.99.100:8002 - this will authenticate our users with OAuth 17 | * Client: 192.168.99.100 - A client which will authenticate via OAuth and make requests to the upstream services 18 | * Service1: 192.168.99.100:8003 - An upstream service 19 | * Service2: 192.168.99.100:8004 - Another upstream service 20 | 21 | * Kong Dashboard: 192.168.99.100:8999 - A dashboard for administrating Kong 22 | 23 | ### Register our upstream APIs: 24 | 25 | To get setup quickly, there are two bash scripts. To register our upstream services, you can run: 26 | 27 | ``` 28 | sh ./register.sh {host} 29 | ``` 30 | 31 | for example: 32 | 33 | ``` 34 | sh ./register.sh '192.168.99.100' 35 | ``` 36 | 37 | This will register both our services. It will spit out the json response. To add oauth. Now, take note of the id's and run: 38 | 39 | ``` 40 | sh ./register2.sh {service1.id} {service2.id} 41 | ``` 42 | 43 | for example.: 44 | 45 | ``` 46 | sh ./register2.sh 0d35c547-1311-4343-a567-7ca670d35637 7e9b3d3e-edc7-4c17-81d0-3f2eac91aaaf 47 | ``` 48 | 49 | Take note of the `provision_id` for service1: At the bottom of `userservice/userservice.settings.py`, set the provision id in `OAUTH_SERVICE.provision_key` 50 | 51 | You can now explore around the Kong Dashboard app (running on port 8999), and you should see that both our downstream APIs have been added, and that they each have the oauth2 plugin added to them. 52 | 53 | ### Register a client application 54 | 55 | Ok. Next up, we need to register a client application which a user can give authority to access upstream APIs on their behalf. 56 | 57 | We're using Django for our backend user authentication. Let's quickly create an admin user in django: 58 | 59 | ``` 60 | docker-compose run --rm userservice python manage.py createsuperuser 61 | ``` 62 | 63 | Now, let's go to: 192.168.99.100:8002/application 64 | 65 | This should ask you to login. Use the user we just created above. 66 | After you've logged in, you'll be redirected back to the applications page. 67 | 68 | This will automatically create a consumer in Kong for the current logged in user (in Django). 69 | 70 | From this page, you can create a client. 71 | 72 | So, at this point we have the following in our Kong setup: 73 | 74 | * 2x upstream APIs with oauth2 authentication. 75 | * A consumer which is linked to our consumer in our Django backend. 76 | * A client application which is registered against our consumer. 77 | 78 | Now we are ready to authenticate our client using oauth2. 79 | 80 | Edit the file `environment.env` set the `client_id` and `client_secret` to match the values associated with the client application you just created. 81 | 82 | Once you've done that: 83 | 84 | * log out of the userservice: http://192.168.99.100:8002/logout/ 85 | * Restart our client application (to make the environment variables apply): 86 | 87 | ``` 88 | docker-compose stop client 89 | docker-compose rm -v client 90 | docker-compose start client 91 | ``` 92 | 93 | Now let's try authenticate. Go to 192.168.99.100 94 | 95 | 1. Click _authenticate_ 96 | 2. You're sent to the userservice to authenticate. Because you're logged out, it will ask you to login. You can use the user above to login again. 97 | 3. Now it will ask you to authorize the client app. Click authorize. 98 | 99 | You should be successfully authenticated and returned to the client again. This time the client will spit out the response from our two upstream services. 100 | 101 | Congrats. You've authenticated using oauth :) 102 | 103 | **Progress** 104 | 105 | * [x] MicroService's with docker-compose 106 | * [x] Manually register all services + verify how OAuth plugin functions 107 | * [x] Create actual authentication backend which interacts with Kong OAuth using Django's user management system 108 | * [x] Automate initial API registration etc via shell script / python script / go script. 109 | * [x] Add the ability to create the OAuth Client Application 110 | * [x] Add the client part of the puzzle 111 | * [x] Make it look nice (sort-of) 112 | * [x] Write linked blog post 113 | * [ ] [Profit](http://www.lstreetc.com/wp-content/uploads/2014/04/Underpants-Gnomes.png) 114 | 115 | 116 | 117 | **Questions** 118 | 119 | 1. **Q:** If I get an access token for a downstream API, is it useable on other downstream APIs?
    120 | **A:** Yes 121 | 1. ... --------------------------------------------------------------------------------