├── src ├── app.php ├── Routes │ ├── Open │ │ ├── RootRouteProvider.php │ │ └── AuthenticateProvider.php │ └── Closed │ │ └── ApiRouteProvider.php └── controllers.php ├── favicon.ico ├── web ├── favicon.ico └── index.php ├── .gitignore ├── test └── e2e-tests │ ├── EmptyTest.php │ ├── RootTest.php │ ├── AuthenticateTest.php │ └── ApiTest.php ├── composer.json ├── gruntfile.js ├── package.json ├── phpunit.xml ├── LICENSE ├── .travis.yml └── README.md /src/app.php: -------------------------------------------------------------------------------- 1 | run(); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files Generated by PHPStorm 2 | .idea/ 3 | .vagrant/ 4 | Vagrantfile 5 | 6 | # Files generated by composer.json 7 | vendor/ 8 | composer.lock 9 | 10 | # Node stuff (Grunt, etc) 11 | npm-debug.log 12 | node_modules/ 13 | -------------------------------------------------------------------------------- /test/e2e-tests/EmptyTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($helloWorldString,'HelloWorld'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "da-gopherboy/silex-jwt-rest-php", 3 | "description": "Sample Silex REST Api Application with Json Web Token Authentication", 4 | "license": "MIT", 5 | "require": { 6 | "silex/silex" : "~1.3", 7 | "firebase/php-jwt" : "~3.0" 8 | }, 9 | "require-dev": { 10 | "phpunit/phpunit": ">=4.3.0", 11 | "symfony/browser-kit": "~3.0", 12 | "codeclimate/php-test-reporter": "dev-master" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "DaGopherboy\\SilexJWTRestPhp\\": "src/" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Routes/Open/RootRouteProvider.php: -------------------------------------------------------------------------------- 1 | get('/', function () use ($app) { 14 | return $app->json([ 15 | 'status' => 0, 16 | 'message' => 'Access Denied' 17 | ]); 18 | }); 19 | return $route; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | watch: { 4 | files: ["./src/**/*.php", "./test/**/*.php"], 5 | tasks: ["shell"] 6 | }, 7 | shell: { 8 | options: { 9 | // Set as true if you want to see warning/errors in the window 10 | stderr: false 11 | }, 12 | target: { 13 | command: 'phpunit' 14 | } 15 | } 16 | }); 17 | grunt.loadNpmTasks("grunt-contrib-watch"); 18 | grunt.loadNpmTasks('grunt-shell'); 19 | grunt.registerTask('default', ['watch']); 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "silex-jwt-rest-php", 3 | "description": "Silex Framework - REST Api with JWT Authentication", 4 | "version": "0.0.1", 5 | "author": "J. Gavin Ray", 6 | "license": "MIT", 7 | "homepage": "https://github.com/DaGopherboy/Silex-JWT-Rest-Php", 8 | "main": "gruntfile.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/DaGopherboy/Silex-JWT-Rest-Php.git" 12 | }, 13 | "devDependencies": { 14 | "grunt": "~0.4.5", 15 | "grunt-phpunit": "~0.3.6", 16 | "grunt-contrib-watch": "~0.4.0", 17 | "grunt-shell": "~1.1.1" 18 | }, 19 | "directories": { 20 | "test": "test" 21 | }, 22 | "scripts": { 23 | "test": "phpunit" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/controllers.php: -------------------------------------------------------------------------------- 1 | mount('/', new RootRouteProvider); 9 | $app->mount('/api', new ApiRouteProvider); 10 | $app->match('/authenticate', "DaGopherboy\SilexJWTRestPhp\Routes\Open\AuthenticateProvider::authenticate"); 11 | 12 | $app->error(function (\Exception $e, $code) { 13 | switch ($code) { 14 | case 404: 15 | $message = 'The requested page could not be found.'; 16 | break; 17 | default: 18 | $message = "We are sorry, but something went terribly wrong. $e"; 19 | } 20 | return new Response($message, $code); 21 | }); 22 | 23 | return $app; 24 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | test/e2e-tests/EmptyTest.php 13 | 14 | 15 | test/e2e-tests/ApiTest.php 16 | 17 | 18 | test/e2e-tests/RootTest.php 19 | 20 | 21 | test/e2e-tests/AuthenticateTest.php 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/e2e-tests/RootTest.php: -------------------------------------------------------------------------------- 1 | app = $app; 16 | } 17 | 18 | public function testRootForStatus() { 19 | $client = $this->createClient(); 20 | 21 | $client->request('GET', '/'); 22 | $response = json_decode($client->getResponse()->getContent()); 23 | $this->assertEquals($response->status,0); 24 | } 25 | 26 | public function testRootForMessage() { 27 | $client = $this->createClient(); 28 | 29 | $client->request('GET', '/'); 30 | $response = json_decode($client->getResponse()->getContent()); 31 | $this->assertEquals($response->message,'Access Denied'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 J. Gavin Ray 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 | 23 | -------------------------------------------------------------------------------- /test/e2e-tests/AuthenticateTest.php: -------------------------------------------------------------------------------- 1 | app = $app; 16 | } 17 | 18 | public function testAuthenticateCheckStatus() { 19 | $client = $this->createClient(); 20 | $userDetails = ['username' => 'test', 21 | 'password' => 'test', 22 | ]; 23 | $client->request('Post', '/authenticate',$userDetails); 24 | $response = json_decode($client->getResponse()->getContent()); 25 | $this->assertEquals($response->status,1); 26 | } 27 | 28 | public function testAuthenticateCheckStatusCode() { 29 | $client = $this->createClient(); 30 | $client->request('Post', '/authenticate'); 31 | $this->assertEquals(200,$client->getResponse()->getStatusCode()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # see http://about.travis-ci.org/docs/user/languages/php/ for more hints 2 | language: php 3 | 4 | # list any PHP version you want to test against 5 | php: 6 | # using major version aliases 7 | 8 | # aliased to a recent 5.5.x version 9 | - 5.5 10 | # aliased to a recent 5.6.x version 11 | - 5.6 12 | 13 | # aliased to a recent 7.0.x version 14 | - 7.0 15 | 16 | # optionally specify a list of environments, for example to test different RDBMS 17 | #env: 18 | # - DB=mysql 19 | # - DB=pgsql 20 | 21 | # execute any number of scripts before the test run, custom env's are available as variables 22 | before_script: composer install 23 | # - if [[ "$DB" == "pgsql" ]]; then psql -c "DROP DATABASE IF EXISTS hello_world_test;" -U postgres; fi 24 | # - if [[ "$DB" == "pgsql" ]]; then psql -c "create database hello_world_test;" -U postgres; fi 25 | # - if [[ "$DB" == "mysql" ]]; then mysql -e "create database IF NOT EXISTS hello_world_test;" -uroot; fi 26 | 27 | # omitting "script:" will default to phpunit 28 | # use the $DB env variable to determine the phpunit.xml to use 29 | #script: phpunit --configuration phpunit.xml --coverage-text 30 | #script: phpunit --configuration phpunit.xml --coverage-text 31 | script: phpunit --coverage-clover build/logs/clover.xml 32 | 33 | # configure notifications (email, IRC, campfire etc) 34 | #notifications: 35 | #irc: "irc.freenode.org#yourfavouriteroomfortravis" 36 | 37 | addons: 38 | code_climate: 39 | repo_token: 2796569e44098c8429285a101fde23d8b76145d140d08d03d0cf14e5d599cb23 40 | -------------------------------------------------------------------------------- /src/Routes/Closed/ApiRouteProvider.php: -------------------------------------------------------------------------------- 1 | headers->get('Authorization'); 22 | if (strpos($rawHeader, 'Bearer ') === false) { 23 | return new Response('Unauthorized', 401); 24 | } 25 | 26 | $headerWithoutBearer = str_replace('Bearer ', '', $rawHeader); 27 | 28 | // Get the secret key for signing the JWT from an environment variable 29 | $someSuperSecretKey = getenv('SomeSuperSecretKey'); 30 | 31 | // If no environment variable is set, use this one. 32 | if(empty($someSuperSecretKey)) { 33 | $someSuperSecretKey = '123456789'; 34 | } 35 | 36 | try { 37 | $decodedJWT = JWT::decode($headerWithoutBearer, $someSuperSecretKey, ['HS256']); 38 | } catch (Exception $e) { 39 | return new Response('Unauthorized', 401); 40 | } 41 | 42 | $app['payload'] = $decodedJWT->payload; 43 | }; 44 | 45 | $route->get('/', function () use ($app) { 46 | return $app->json([ 47 | 'status' => 1, 48 | 'message' => json_encode($app['payload']) 49 | ]); 50 | })->before($before); 51 | 52 | return $route; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Routes/Open/AuthenticateProvider.php: -------------------------------------------------------------------------------- 1 | get('username'); 14 | $password = $request->get('password'); 15 | 16 | if ($username == 'test' && $password == 'test') { 17 | $jsonObject = array( 18 | // Registered Claims 19 | "iss" => "DaGopherboy", // Claiming Issure 20 | "aud" => "https://github.com/DaGopherboy/Silex-JWT-Rest-Php", // Intended Audience 21 | "iat" => time(), // Issued At Time 22 | "nbf" => time(), // Not Before Time 23 | "exp" => time()+60*60*24, // Expiration Time (24 hours) 24 | // Public Claims 25 | "payload" => [ 26 | "firstName" => "Test", 27 | "lastName" => "Tester", 28 | "title" => "Head of Quality Assurance", 29 | "admin" => true 30 | ] 31 | ); 32 | 33 | // Get the secret key for signing the JWT from an environment variable 34 | $someSuperSecretKey = getenv('SomeSuperSecretKey'); 35 | 36 | // If no environment variable is set, use this one. 37 | if(empty($someSuperSecretKey)) { 38 | $someSuperSecretKey = '123456789'; 39 | } 40 | 41 | // Sign the JWT with the secret key 42 | $jsonWebToken = JWT::encode($jsonObject, $someSuperSecretKey); 43 | 44 | return $app->json([ 45 | 'status' => 1, 46 | 'message' => $jsonWebToken 47 | ]); 48 | } else { 49 | 50 | return $app->json(['status' => 0, 51 | 'message' => 'Failed to Authenticate']); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Silex-JWT-Rest-Php 2 | Sample Project: 3 | 4 | PHP 5.5+ project that utilizes the Silex Framework. This is a bare bones REST Api that uses JSON Web Tokens for Authentication. 5 | 6 | [![Build Status](https://travis-ci.org/jgavinray/Silex-JWT-Rest-Php.svg?branch=master)](https://travis-ci.org/jgavinray/Silex-JWT-Rest-Php) 7 | [![Code Climate](https://codeclimate.com/github/DaGopherboy/Silex-JWT-Rest-Php/badges/gpa.svg)](https://codeclimate.com/github/DaGopherboy/Silex-JWT-Rest-Php) 8 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/fb66a40c-78a5-4950-8c16-dd6e4e73952b/mini.png)](https://insight.sensiolabs.com/projects/fb66a40c-78a5-4950-8c16-dd6e4e73952b) 9 | [![Dependency Status](https://www.versioneye.com/user/projects/554970425d4f9a0b990012db/badge.svg?style=flat)](https://www.versioneye.com/user/projects/554970425d4f9a0b990012db) 10 | 11 | ## Getting Started 12 | There are only three basic requirements at the moment to utilize this Demo project. The first is git ([https://git-scm.com/](https://git-scm.com/)), 13 | the second is composer ([https://getcomposer.org](https://getcomposer.org)), and the third is PHPUnit ([https://phpunit.de](https://phpunit.de)). 14 | With these three tools the project is relatively easy to get going and exercise. The commands listed below have been 15 | tested on Windows, Linux, and OS X terminals. 16 | 17 | ### Step One - Get the source 18 | 19 | ``` 20 | $ git clone https://github.com/DaGopherboy/Silex-JWT-Rest-Php.git 21 | ``` 22 | 23 | ### Step two - Get into the same directory as the source code 24 | 25 | ``` 26 | $ cd Silex-JWT-Rest-Php 27 | ``` 28 | 29 | ### Step three - Update all of the dependencies 30 | 31 | ``` 32 | $ composer update 33 | ``` 34 | 35 | ### Step four - Run the tests 36 | 37 | ``` 38 | $ phpunit 39 | ``` 40 | 41 | 42 | ## Why do this? There are so many other options! 43 | 44 | Good question, there are many great frameworks out there that have authentication modules that can make bootstrapping 45 | a new api a snap. What I wanted to do was have a very minimalist bootstrap project that I can pull off the shelf and 46 | start writing code to prototype something. In addition to that goal, this project has provided a fantastic learning 47 | opportunity of how to use JSON Web Tokens ([http://jwt.io](http://jwt.io)); And how they can play a part in a security 48 | strategy for REST APIs. 49 | 50 | ## Contributions 51 | 52 | If you see anything that can be improved upon, and are interested in contributing please feel free to submit a pull 53 | request. 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/e2e-tests/ApiTest.php: -------------------------------------------------------------------------------- 1 | app = $app; 20 | } 21 | 22 | public function testApiForStatus() { 23 | if(empty($this->jwt)){ 24 | $this->getTokenWithValidUsernameAndPassword(); 25 | } 26 | 27 | $client = $this->createClient(); 28 | 29 | $client->request('GET', 30 | '/api/', 31 | [], 32 | [], 33 | ['HTTP_AUTHORIZATION' => "Bearer $this->jwt"] 34 | ); 35 | $response = json_decode($client->getResponse()->getContent()); 36 | $this->assertEquals($response->status, 1); 37 | } 38 | 39 | public function testApiForMessage() { 40 | if(empty($this->jwt)){ 41 | $this->getTokenWithValidUsernameAndPassword(); 42 | } 43 | 44 | $client = $this->createClient(); 45 | 46 | $client->request('GET', 47 | '/api/', 48 | [], 49 | [], 50 | ['HTTP_AUTHORIZATION' => "Bearer $this->jwt"] 51 | ); 52 | $response = json_decode($client->getResponse()->getContent()); 53 | $this->assertEquals($response->message, '{"firstName":"Test","lastName":"Tester","title":"Head of Quality Assurance","admin":true}'); 54 | } 55 | 56 | public function testApiWithoutJWTCheckFor401Status() { 57 | $client = $this->createClient(); 58 | 59 | $client->request('GET', '/api/'); 60 | $response = $client->getResponse()->getStatusCode(); 61 | $this->assertEquals($response, 401); 62 | } 63 | 64 | public function testApiWithoutJWTCheckForUnauthorizedContent() { 65 | $client = $this->createClient(); 66 | 67 | $client->request('GET', '/api/'); 68 | $response = $client->getResponse()->getContent(); 69 | $this->assertEquals($response, 'Unauthorized'); 70 | } 71 | public function getTokenWithValidUsernameAndPassword() { 72 | $client = $this->createClient(); 73 | $userDetails = ['username' => $this->validuser, 74 | 'password' => $this->validpassword, 75 | ]; 76 | $client->request('POST', '/authenticate', $userDetails); 77 | $response = json_decode($client->getResponse()->getContent()); 78 | $this->jwt = $response->message; 79 | } 80 | } 81 | --------------------------------------------------------------------------------