├── .env.example ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── Comment.php ├── Console │ ├── Commands │ │ └── .gitkeep │ └── Kernel.php ├── Events │ ├── Event.php │ └── ExampleEvent.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── CommentController.php │ │ ├── Controller.php │ │ ├── ExampleController.php │ │ ├── PostCommentController.php │ │ ├── PostController.php │ │ └── UserController.php │ └── Middleware │ │ ├── Authenticate.php │ │ ├── Authorize.php │ │ └── ExampleMiddleware.php ├── Jobs │ ├── ExampleJob.php │ └── Job.php ├── Listeners │ └── ExampleListener.php ├── Post.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ └── EventServiceProvider.php └── User.php ├── artisan ├── bootstrap └── app.php ├── composer.json ├── config └── oauth2.php ├── database ├── factories │ └── ModelFactory.php ├── migrations │ ├── .gitkeep │ ├── 2014_04_24_110151_create_oauth_scopes_table.php │ ├── 2014_04_24_110304_create_oauth_grants_table.php │ ├── 2014_04_24_110403_create_oauth_grant_scopes_table.php │ ├── 2014_04_24_110459_create_oauth_clients_table.php │ ├── 2014_04_24_110557_create_oauth_client_endpoints_table.php │ ├── 2014_04_24_110705_create_oauth_client_scopes_table.php │ ├── 2014_04_24_110817_create_oauth_client_grants_table.php │ ├── 2014_04_24_111002_create_oauth_sessions_table.php │ ├── 2014_04_24_111109_create_oauth_session_scopes_table.php │ ├── 2014_04_24_111254_create_oauth_auth_codes_table.php │ ├── 2014_04_24_111403_create_oauth_auth_code_scopes_table.php │ ├── 2014_04_24_111518_create_oauth_access_tokens_table.php │ ├── 2014_04_24_111657_create_oauth_access_token_scopes_table.php │ ├── 2014_04_24_111810_create_oauth_refresh_tokens_table.php │ ├── 2016_03_24_182334_create_users_table.php │ ├── 2016_03_24_221425_create_posts_table.php │ └── 2016_03_24_221457_create_comments_table.php └── seeds │ ├── DatabaseSeeder.php │ └── OAuthClientSeeder.php ├── phpunit.xml ├── public ├── .htaccess ├── index.php └── lumen-api-oauth.png ├── resources └── views │ └── .gitkeep ├── routes └── web.php ├── storage ├── app │ └── .gitignore ├── framework │ ├── cache │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore └── tests ├── ExampleTest.php └── TestCase.php /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_DEBUG=true 3 | APP_KEY= 4 | APP_TIMEZONE=UTC 5 | 6 | DB_CONNECTION=mysql 7 | DB_HOST=127.0.0.1 8 | DB_PORT=3306 9 | DB_DATABASE=homestead 10 | DB_USERNAME=homestead 11 | DB_PASSWORD=secret 12 | 13 | CACHE_DRIVER=array 14 | QUEUE_DRIVER=array 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | Homestead.json 4 | Homestead.yaml 5 | .env 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - hhvm 7 | 8 | sudo: false 9 | 10 | install: travis_retry composer install --no-interaction --prefer-source 11 | 12 | script: vendor/bin/phpunit 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Omar El Gabry 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Lumen API OAuth 3 |

4 | 5 | # Lumen API OAuth 6 | [![Build Status](https://travis-ci.org/OmarElGabry/lumen-api-oauth.png)](https://travis-ci.org/OmarElGabry/lumen-api-oauth) 7 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/OmarElGabry/lumen-api-oauth/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/OmarElGabry/lumen-api-oauth/?branch=master) 8 | [![Code Climate](https://codeclimate.com/github/OmarElGabry/lumen-api-oauth/badges/gpa.svg)](https://codeclimate.com/github/OmarElGabry/lumen-api-oauth) 9 | [![Dependency Status](https://www.versioneye.com/user/projects/57060d31fcd19a0039f15da4/badge.svg?style=flat)](https://www.versioneye.com/user/projects/57060d31fcd19a0039f15da4) 10 | 11 | [![Latest Stable Version](https://poser.pugx.org/omarelgabry/lumen-api-oauth/v/stable)](https://packagist.org/packages/omarelgabry/lumen-api-oauth) 12 | [![License](https://poser.pugx.org/omarelgabry/lumen-api-oauth/license)](https://packagist.org/packages/omarelgabry/lumen-api-oauth) 13 | 14 | A RESTful API based on Lumen micro-framework with OAuth2. Lumen API OAuth is a simple application, indented for small projects, helps to understand creating RESTful APIs with Lumen and OAuth2, know how to authenticate and authorize, and more. 15 | 16 | The RESTful API for Posts and Comments, where Users can view, create, update, and delete. It provides authorization mechanism to authorize against access tokens using OAuth2, ownership, and non-admin Vs admin users. 17 | 18 | :mega: A full tutorial on building a RESTful API with Lumen and OAuth2 can be found on [Medium](https://medium.com/omarelgabrys-blog/building-restful-apis-with-lumen-and-oauth2-8ba279c6a31). 19 | 20 | ## Index 21 | + [Installation](#installation) 22 | + [Terminology](#terminology) 23 | + [Authorization](#authorization) 24 | + [Routing](#routing) 25 | + [Support](#support) 26 | + [Contribute](#contribute) 27 | + [Dependencies](#dependencies) 28 | + [License](#license) 29 | 30 | ## Installation 31 | Steps: 32 | 33 | 1. Run [Composer](https://getcomposer.org/doc/00-intro.md) 34 | 35 | ``` 36 | composer install 37 | ``` 38 | 2. Laravel Homestead 39 | 40 | If you are using Laravel Homestead, then follow the [Installation Guide](https://laravel.com/docs/5.2/homestead). 41 | 42 | 3. WAMP, LAMP, MAMP, XAMP Server 43 | 44 | If you are using any of WAMP, LAMP, MAMP, XAMP Servers, then don't forget to create a database, probably a MySQL database. 45 | 46 | 4. Configure the```.env``` file 47 | 48 | Rename ```.env.example``` file to ```.env```, set your application key to a random string with 32 characters long, edit database name, database username, and database password if needed. 49 | 50 | 5. Finally, Run Migrations and Seed the database with fake data. 51 | 52 | ``` 53 | php artisan migrate --seed 54 | ``` 55 | 56 | ## Terminology 57 | There are some terminologies that will be used on the meaning of the terms used by OAuth 2.0. If you need a refresher, then check [this](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2) out. 58 | 59 | ## Authorization 60 | Authorization comes in two layers. The first layer authorize against the access token, and the second one is for checking against ownership, and non-admin Vs admin users. 61 | 62 | By default, user can delete or update a post or a comment **only** if he is the owner. Admins are authorized to view, create, update or delete anything. 63 | 64 | ### Access Tokens 65 | The application implements [Resource owner credentials grant](https://github.com/lucadegasperi/oauth2-server-laravel/blob/master/docs/authorization-server/choosing-grant.md#resource-owner-credentials-grant-section-43), which essentially requires the client to submit 5 fields: ```username```, ```password```, ```client_id```, ```client_secret```, and ```grant_type```. 66 | 67 | The authorization server will then issue access tokens to the client after successfully authenticating the client credentials and presenting authorization grant(user credentials). 68 | 69 | In ```app/Http/routes.php```, A route has been defined for requesting an access token. 70 | 71 | ### Ownership, & non-Admin Vs Admin Users 72 | Now, after validating the access token, we can extend the authorization layers and check if the current user is owner of the requested resource(i.e. post or comment), or is admin. So, _How does it work?_ 73 | 74 | **Assign Middleware to controller** 75 | ```php 76 | public function __construct(){ 77 | 78 | $this->middleware('oauth', ['except' => ['index', 'show']]); 79 | $this->middleware('authorize:' . __CLASS__, ['except' => ['index', 'show', 'store']]); 80 | } 81 | 82 | ``` 83 | 84 | **Order** 85 | 86 | Please note that the middlewares has to be applied in a certain order. The ```oauth``` has to be added before the ```authorize``` Middleware. 87 | 88 | **Override isAuthorized() method** 89 | ```php 90 | public function isAuthorized(Request $request){ 91 | 92 | $resource = "posts"; 93 | $post = Post::find($this->getArgs($request)["post_id"]); 94 | 95 | return $this->authorizeUser($request, $resource, $post); 96 | } 97 | ``` 98 | 99 | In ```app/Providers/AuthServiceProvider.php```, Abilities are defined using ```Gate``` facade. 100 | 101 | ## Routing 102 | These are some of the routes defined in ```app/routes.php```. You can test the API using [Postman](https://www.getpostman.com/) 103 | 104 | | HTTP Method | Path | Action | Fields | 105 | | ----- | ----- | ----- | ------------- | 106 | | GET | /users | index | 107 | | POST | /oauth/access_token | | username, password, client_id, client_secret, and grant_type.
_The ```username``` field is the ```email``` in ```Users``` table_.
_The ```password``` field is **secret**_.
_The ```client_id``` & ```client_secret``` fields are **id0** & **secret0**, or **id1** & **secret1**, ...etc respectively_.
_The ```grant_type``` field is **password**_. 108 | | POST | /posts | store | access_token, title, content 109 | | PUT | /posts/{post_id} | update | access_token, title, content 110 | | DELETE | /posts/{post_id} | destroy | access_token 111 | 112 | 113 | ## Support 114 | I've written this script in my free time during my studies. This is for free, unpaid. If you find it useful, please support the project by spreading the word. 115 | 116 | ## Contribute 117 | 118 | Contribute by creating new issues, sending pull requests on Github or you can send an email at: omar.elgabry.93@gmail.com 119 | 120 | ## Dependencies 121 | + [OAuth2 Server](https://github.com/lucadegasperi/oauth2-server-laravel/) 122 | 123 | ## License 124 | Built under [MIT](http://www.opensource.org/licenses/mit-license.php) license. 125 | -------------------------------------------------------------------------------- /app/Comment.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Post'); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/Console/Commands/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/lumen-api-oauth/75a8a1e70d9b8942631bd4d8f505f750d4d4b573/app/Console/Commands/.gitkeep -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | json(['message' => 'Bad Request', 'code' => 400], 400); 57 | } 58 | 59 | if($e instanceof MethodNotAllowedHttpException){ 60 | return response()->json(['message' => 'Not Found', 'code' => 404], 404); 61 | } 62 | 63 | return response()->json(['message' => 'Unexpected Error', 'code' => 500], 500); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Http/Controllers/CommentController.php: -------------------------------------------------------------------------------- 1 | success($comments, 200); 13 | } 14 | 15 | public function show($id){ 16 | 17 | $comment = Comment::find($id); 18 | 19 | if(!$comment){ 20 | return $this->error("The comment with {$id} doesn't exist", 404); 21 | } 22 | 23 | return $this->success($comment, 200); 24 | } 25 | } -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | json(['data' => $data], $code); 21 | } 22 | 23 | /** 24 | * Return a JSON response for error. 25 | * 26 | * @param array $message 27 | * @param string $code 28 | * @return \Illuminate\Http\JsonResponse 29 | */ 30 | public function error($message, $code){ 31 | return response()->json(['message' => $message], $code); 32 | } 33 | 34 | /** 35 | * Check if the user is authorized to perform a given action on a resource. 36 | * 37 | * @param \Illuminate\Http\Request $request 38 | * @param array $resource 39 | * @param mixed|array $arguments 40 | * @return boolean 41 | * @see https://lumen.laravel.com/docs/authorization 42 | */ 43 | protected function authorizeUser(Request $request, $resource, $arguments = []){ 44 | 45 | $user = User::find($this->getUserId()); 46 | $action = $this->getAction($request); 47 | 48 | // The ability string must match the string defined in App\Providers\AuthServiceProvider\ability() 49 | $ability = "{$action}-{$resource}"; 50 | 51 | // return $this->authorizeForUser($user, "{$action}-{$resource}", $data); 52 | return Gate::forUser($user)->allows($ability, $arguments); 53 | } 54 | 55 | /** 56 | * Check if user is authorized. 57 | * 58 | * This method will be called by "Authorize" Middleware for every controller. 59 | * Controller that needs to be authorized must override this method. 60 | * 61 | * @param \Illuminate\Http\Request $request 62 | * @return bool 63 | */ 64 | public function isAuthorized(Request $request){ 65 | return false; 66 | } 67 | 68 | /** 69 | * Get current authorized user id. 70 | * This method should be called only after validating the access token using OAuthMiddleware Middleware. 71 | * 72 | * @return boolean 73 | */ 74 | protected function getUserId(){ 75 | return \LucaDegasperi\OAuth2Server\Facades\Authorizer::getResourceOwnerId(); 76 | } 77 | 78 | /** 79 | * Get the requested action method. 80 | * 81 | * @param \Illuminate\Http\Request $request 82 | * @return string 83 | */ 84 | protected function getAction(Request $request){ 85 | return explode('@', $request->route()[1]["uses"], 2)[1]; 86 | } 87 | 88 | /** 89 | * Get the parameters in route. 90 | * 91 | * @param \Illuminate\Http\Request $request 92 | * @return array 93 | */ 94 | protected function getArgs(Request $request){ 95 | return $request->route()[2]; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/Http/Controllers/ExampleController.php: -------------------------------------------------------------------------------- 1 | middleware('oauth', ['except' => ['index', 'show']]); 15 | $this->middleware('authorize:' . __CLASS__, ['except' => ['index', 'show', 'store']]); 16 | } 17 | 18 | public function index($post_id){ 19 | 20 | $post = Post::find($post_id); 21 | 22 | if(!$post){ 23 | return $this->error("The post with {$post_id} doesn't exist", 404); 24 | } 25 | 26 | $comments = $post->comments; 27 | return $this->success($comments, 200); 28 | } 29 | 30 | public function store(Request $request, $post_id){ 31 | 32 | $post = Post::find($post_id); 33 | 34 | if(!$post){ 35 | return $this->error("The post with {$post_id} doesn't exist", 404); 36 | } 37 | 38 | $this->validateRequest($request); 39 | 40 | $comment = Comment::create([ 41 | 'content' => $request->get('content'), 42 | 'user_id'=> $this->getUserId(), 43 | 'post_id'=> $post_id 44 | ]); 45 | 46 | return $this->success("The comment with id {$comment->id} has been created and assigned to the post with id {$post_id}", 201); 47 | } 48 | 49 | public function update(Request $request, $post_id, $comment_id){ 50 | 51 | $comment = Comment::find($comment_id); 52 | $post = Post::find($post_id); 53 | 54 | if(!$comment || !$post){ 55 | return $this->error("The comment with {$comment_id} or the post with id {$post_id} doesn't exist", 404); 56 | } 57 | 58 | $this->validateRequest($request); 59 | 60 | $comment->content = $request->get('content'); 61 | $comment->user_id = $this->getUserId(); 62 | $comment->post_id = $post_id; 63 | 64 | $comment->save(); 65 | 66 | return $this->success("The comment with with id {$comment->id} has been updated", 200); 67 | } 68 | 69 | public function destroy($post_id, $comment_id){ 70 | 71 | $comment = Comment::find($comment_id); 72 | $post = Post::find($post_id); 73 | 74 | if(!$comment || !$post){ 75 | return $this->error("The comment with {$comment_id} or the post with id {$post_id} doesn't exist", 404); 76 | } 77 | 78 | if(!$post->comments()->find($comment_id)){ 79 | return $this->error("The comment with id {$comment_id} isn't assigned to the post with id {$post_id}", 409); 80 | } 81 | 82 | $comment->delete(); 83 | 84 | return $this->success("The comment with id {$comment_id} has been removed of the post {$post_id}", 200); 85 | } 86 | 87 | public function validateRequest(Request $request){ 88 | 89 | $rules = [ 90 | 'content' => 'required' 91 | ]; 92 | 93 | $this->validate($request, $rules); 94 | } 95 | 96 | public function isAuthorized(Request $request){ 97 | 98 | $resource = "comments"; 99 | $comment = Comment::find($this->getArgs($request)["comment_id"]); 100 | 101 | return $this->authorizeUser($request, $resource, $comment); 102 | } 103 | } -------------------------------------------------------------------------------- /app/Http/Controllers/PostController.php: -------------------------------------------------------------------------------- 1 | middleware('oauth', ['except' => ['index', 'show']]); 14 | $this->middleware('authorize:' . __CLASS__, ['except' => ['index', 'show', 'store']]); 15 | } 16 | 17 | public function index(){ 18 | 19 | $posts = Post::all(); 20 | return $this->success($posts, 200); 21 | } 22 | 23 | public function store(Request $request){ 24 | 25 | $this->validateRequest($request); 26 | 27 | $post = Post::create([ 28 | 'title' => $request->get('title'), 29 | 'content'=> $request->get('content'), 30 | 'user_id' => $this->getUserId() 31 | ]); 32 | 33 | return $this->success("The post with with id {$post->id} has been created", 201); 34 | } 35 | 36 | public function show($id){ 37 | 38 | $post = Post::find($id); 39 | 40 | if(!$post){ 41 | return $this->error("The post with {$id} doesn't exist", 404); 42 | } 43 | 44 | return $this->success($post, 200); 45 | } 46 | 47 | public function update(Request $request, $id){ 48 | 49 | $post = Post::find($id); 50 | 51 | if(!$post){ 52 | return $this->error("The post with {$id} doesn't exist", 404); 53 | } 54 | 55 | $this->validateRequest($request); 56 | 57 | $post->title = $request->get('title'); 58 | $post->content = $request->get('content'); 59 | $post->user_id = $this->getUserId(); 60 | 61 | $post->save(); 62 | 63 | return $this->success("The post with with id {$post->id} has been updated", 200); 64 | } 65 | 66 | public function destroy($id){ 67 | 68 | $post = Post::find($id); 69 | 70 | if(!$post){ 71 | return $this->error("The post with {$id} doesn't exist", 404); 72 | } 73 | 74 | // no need to delete the comments for the current post, 75 | // since we used on delete cascase on update cascase. 76 | // $post->comments()->delete(); 77 | $post->delete(); 78 | 79 | return $this->success("The post with with id {$id} has been deleted along with it's comments", 200); 80 | } 81 | 82 | public function validateRequest(Request $request){ 83 | 84 | $rules = [ 85 | 'title' => 'required', 86 | 'content' => 'required' 87 | ]; 88 | 89 | $this->validate($request, $rules); 90 | } 91 | 92 | public function isAuthorized(Request $request){ 93 | 94 | $resource = "posts"; 95 | $post = Post::find($this->getArgs($request)["post_id"]); 96 | 97 | return $this->authorizeUser($request, $resource, $post); 98 | } 99 | } -------------------------------------------------------------------------------- /app/Http/Controllers/UserController.php: -------------------------------------------------------------------------------- 1 | middleware('oauth', ['except' => ['index', 'show']]); 15 | $this->middleware('authorize:' . __CLASS__, ['except' => ['index', 'show']]); 16 | } 17 | 18 | public function index(){ 19 | 20 | $users = User::all(); 21 | return $this->success($users, 200); 22 | } 23 | 24 | public function store(Request $request){ 25 | 26 | $this->validateRequest($request); 27 | 28 | $user = User::create([ 29 | 'email' => $request->get('email'), 30 | 'password'=> Hash::make($request->get('password')) 31 | ]); 32 | 33 | return $this->success("The user with with id {$user->id} has been created", 201); 34 | } 35 | 36 | public function show($id){ 37 | 38 | $user = User::find($id); 39 | 40 | if(!$user){ 41 | return $this->error("The user with {$id} doesn't exist", 404); 42 | } 43 | 44 | return $this->success($user, 200); 45 | } 46 | 47 | public function update(Request $request, $id){ 48 | 49 | $user = User::find($id); 50 | 51 | if(!$user){ 52 | return $this->error("The user with {$id} doesn't exist", 404); 53 | } 54 | 55 | $this->validateRequest($request); 56 | 57 | $user->email = $request->get('email'); 58 | $user->password = Hash::make($request->get('password')); 59 | 60 | $user->save(); 61 | 62 | return $this->success("The user with with id {$user->id} has been updated", 200); 63 | } 64 | 65 | public function destroy($id){ 66 | 67 | $user = User::find($id); 68 | 69 | if(!$user){ 70 | return $this->error("The user with {$id} doesn't exist", 404); 71 | } 72 | 73 | $user->delete(); 74 | 75 | return $this->success("The user with with id {$id} has been deleted", 200); 76 | } 77 | 78 | public function validateRequest(Request $request){ 79 | 80 | $rules = [ 81 | 'email' => 'required|email|unique:users', 82 | 'password' => 'required|min:6' 83 | ]; 84 | 85 | $this->validate($request, $rules); 86 | } 87 | 88 | public function isAuthorized(Request $request){ 89 | 90 | $resource = "users"; 91 | // $user = User::find($this->getArgs($request)["user_id"]); 92 | 93 | return $this->authorizeUser($request, $resource); 94 | } 95 | } -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 26 | } 27 | 28 | /** 29 | * Handle an incoming request. 30 | * 31 | * @param \Illuminate\Http\Request $request 32 | * @param \Closure $next 33 | * @param string|null $guard 34 | * @return mixed 35 | */ 36 | public function handle($request, Closure $next, $guard = null) 37 | { 38 | if ($this->auth->guard($guard)->guest()) { 39 | return response('Unauthorized.', 401); 40 | } 41 | 42 | return $next($request); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authorize.php: -------------------------------------------------------------------------------- 1 | isAuthorized($request)){ 19 | return $controller->error("You aren't allowed to perform the requested action", 403); 20 | } 21 | 22 | return $next($request); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Middleware/ExampleMiddleware.php: -------------------------------------------------------------------------------- 1 | hasMany('App\Comment'); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app['auth']->viaRequest('api', function ($request) { 35 | // if ($request->input('api_token')) { 36 | // return User::where('api_token', $request->input('api_token'))->first(); 37 | // } 38 | // }); 39 | 40 | // Group & Define simillar Abilities 41 | $this->isOwner([ 42 | 'posts' => ['destroy', 'update'], 43 | 'comments' => ['destroy', 'update'] 44 | ]); 45 | 46 | $this->isAdmin([ 47 | 'users' => ['store', 'destroy', 'update'] 48 | ]); 49 | } 50 | 51 | /** 52 | * Define abilities that checks if the current user is the owner of the requested resource. 53 | * In case of admin user, it will return true. 54 | * 55 | * @param array $arguments 56 | * @return boolean 57 | */ 58 | private function isOwner($arguments = []){ 59 | 60 | foreach ($arguments as $resource => $actions) { 61 | foreach ($actions as $action) { 62 | 63 | // Gate::before(function ($user, $ability) { 64 | // if($user->is_admin){ 65 | // return true; 66 | // } 67 | // }); 68 | 69 | Gate::define($this->ability($action, $resource), function ($user, $arg) { 70 | 71 | if(is_null($arg)) { return false; } 72 | 73 | return $arg->user_id === $user->id || $user->is_admin; 74 | }); 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Define abilities that checks if the current user is admin. 81 | * 82 | * @param array $arguments 83 | * @return boolean 84 | */ 85 | private function isAdmin($arguments){ 86 | 87 | foreach ($arguments as $resource => $actions) { 88 | foreach ($actions as $action) { 89 | Gate::define($this->ability($action, $resource), function ($user) { 90 | return $user->is_admin; 91 | }); 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * Define ability string. 98 | * 99 | * @param string $action 100 | * @param string $resource 101 | * @return string 102 | */ 103 | private function ability($action, $resource){ 104 | return "{$action}-{$resource}"; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'App\Listeners\EventListener', 17 | ], 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | first(); 46 | 47 | if($user && Hash::check($password, $user->password)){ 48 | return $user->id; 49 | } 50 | 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make( 32 | 'Illuminate\Contracts\Console\Kernel' 33 | ); 34 | 35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput)); 36 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | load(); 7 | } catch (Dotenv\Exception\InvalidPathException $e) { 8 | // 9 | } 10 | 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | Create The Application 14 | |-------------------------------------------------------------------------- 15 | | 16 | | Here we will load the environment and create the application instance 17 | | that serves as the central piece of this framework. We'll use this 18 | | application as an "IoC" container and router for this framework. 19 | | 20 | */ 21 | 22 | $app = new Laravel\Lumen\Application( 23 | realpath(__DIR__.'/../') 24 | ); 25 | 26 | $app->withFacades(); 27 | 28 | $app->withEloquent(); 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Register Container Bindings 33 | |-------------------------------------------------------------------------- 34 | | 35 | | Now we will register a few bindings in the service container. We will 36 | | register the exception handler and the console kernel. You may add 37 | | your own bindings here if you like or you can make another file. 38 | | 39 | */ 40 | 41 | $app->singleton( 42 | Illuminate\Contracts\Debug\ExceptionHandler::class, 43 | App\Exceptions\Handler::class 44 | ); 45 | 46 | $app->singleton( 47 | Illuminate\Contracts\Console\Kernel::class, 48 | App\Console\Kernel::class 49 | ); 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Register Middleware 54 | |-------------------------------------------------------------------------- 55 | | 56 | | Next, we will register the middleware with the application. These can 57 | | be global middleware that run before and after each request into a 58 | | route or middleware that'll be assigned to some specific routes. 59 | | 60 | */ 61 | 62 | $app->middleware([ 63 | // App\Http\Middleware\ExampleMiddleware::class 64 | \LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware::class 65 | ]); 66 | 67 | $app->routeMiddleware([ 68 | // 'auth' => App\Http\Middleware\Authenticate::class, 69 | 'oauth' => \LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware::class, 70 | // 'oauth-user'=> \LucaDegasperi\OAuth2Server\Middleware\OAuthUserOwnerMiddleware::class, 71 | 'authorize' => App\Http\Middleware\Authorize::class, 72 | ]); 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Register Service Providers 77 | |-------------------------------------------------------------------------- 78 | | 79 | | Here we will register all of the application's service providers which 80 | | are used to bind services into the container. Service providers are 81 | | totally optional, so you are not required to uncomment this line. 82 | | 83 | */ 84 | 85 | // $app->register(App\Providers\AppServiceProvider::class); 86 | $app->register(App\Providers\AuthServiceProvider::class); 87 | // $app->register(App\Providers\EventServiceProvider::class); 88 | $app->register(\LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvider::class); 89 | $app->register(\LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider::class); 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Load The Application Routes 94 | |-------------------------------------------------------------------------- 95 | | 96 | | Next we will include the routes file so that they can all be added to 97 | | the application. This will provide all of the URLs the application 98 | | can respond to, as well as the controllers that may handle them. 99 | | 100 | */ 101 | 102 | $app->group(['namespace' => 'App\Http\Controllers'], function ($app) { 103 | require __DIR__.'/../routes/web.php'; 104 | }); 105 | 106 | return $app; 107 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omarelgabry/lumen-api-oauth", 3 | "description": "A RESTful API based on Lumen micro-framework with OAuth2.", 4 | "keywords": ["lumen", "rest", "api", "oauth", "authentication", "authorization"], 5 | "homepage": "https://github.com/OmarElGabry/lumen-api-oauth", 6 | "license": "MIT", 7 | "type": "project", 8 | "require": { 9 | "php": ">=5.6.4", 10 | "laravel/lumen-framework": "5.4.*", 11 | "vlucas/phpdotenv": "~2.2", 12 | "laravel/homestead": "^5.0", 13 | "lucadegasperi/oauth2-server-laravel": "^5.2" 14 | }, 15 | "require-dev": { 16 | "fzaninotto/faker": "~1.4", 17 | "phpunit/phpunit": "~5.0", 18 | "mockery/mockery": "~0.9" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "App\\": "app/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "classmap": [ 27 | "tests/", 28 | "database/" 29 | ] 30 | }, 31 | "scripts": { 32 | "post-root-package-install": [ 33 | "php -r \"copy('.env.example', '.env');\"" 34 | ] 35 | }, 36 | "minimum-stability": "dev", 37 | "prefer-stable": true 38 | } 39 | -------------------------------------------------------------------------------- /config/oauth2.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | return [ 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Supported Grant Types 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Your OAuth2 Server can issue an access token based on different grant 20 | | types you can even provide your own grant type. 21 | | 22 | | To choose which grant type suits your scenario, see 23 | | http://oauth2.thephpleague.com/authorization-server/which-grant 24 | | 25 | | Please see this link to find available grant types 26 | | http://git.io/vJLAv 27 | | 28 | */ 29 | 30 | 'grant_types' => [ 31 | 'password' => [ 32 | 'class' => '\League\OAuth2\Server\Grant\PasswordGrant', 33 | 'callback' => '\App\User@verify', 34 | 'access_token_ttl' => 3600 35 | ] 36 | ], 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Output Token Type 41 | |-------------------------------------------------------------------------- 42 | | 43 | | This will tell the authorization server the output format for the access 44 | | token and the resource server how to parse the access token used. 45 | | 46 | | Default value is League\OAuth2\Server\TokenType\Bearer 47 | | 48 | */ 49 | 50 | 'token_type' => 'League\OAuth2\Server\TokenType\Bearer', 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | State Parameter 55 | |-------------------------------------------------------------------------- 56 | | 57 | | Whether or not the state parameter is required in the query string. 58 | | 59 | */ 60 | 61 | 'state_param' => false, 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | Scope Parameter 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Whether or not the scope parameter is required in the query string. 69 | | 70 | */ 71 | 72 | 'scope_param' => false, 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Scope Delimiter 77 | |-------------------------------------------------------------------------- 78 | | 79 | | Which character to use to split the scope parameter in the query string. 80 | | 81 | */ 82 | 83 | 'scope_delimiter' => ',', 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | Default Scope 88 | |-------------------------------------------------------------------------- 89 | | 90 | | The default scope to use if not present in the query string. 91 | | 92 | */ 93 | 94 | 'default_scope' => null, 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Access Token TTL 99 | |-------------------------------------------------------------------------- 100 | | 101 | | For how long the issued access token is valid (in seconds) this can be 102 | | also set on a per grant-type basis. 103 | | 104 | */ 105 | 106 | 'access_token_ttl' => 3600, 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Limit clients to specific grants 111 | |-------------------------------------------------------------------------- 112 | | 113 | | Whether or not to limit clients to specific grant types. This is useful 114 | | to allow only trusted clients to access your API differently. 115 | | 116 | */ 117 | 118 | 'limit_clients_to_grants' => false, 119 | 120 | /* 121 | |-------------------------------------------------------------------------- 122 | | Limit clients to specific scopes 123 | |-------------------------------------------------------------------------- 124 | | 125 | | Whether or not to limit clients to specific scopes. This is useful to 126 | | only allow specific clients to use some scopes. 127 | | 128 | */ 129 | 130 | 'limit_clients_to_scopes' => false, 131 | 132 | /* 133 | |-------------------------------------------------------------------------- 134 | | Limit scopes to specific grants 135 | |-------------------------------------------------------------------------- 136 | | 137 | | Whether or not to limit scopes to specific grants. This is useful to 138 | | allow certain scopes to be used only with certain grant types. 139 | | 140 | */ 141 | 142 | 'limit_scopes_to_grants' => false, 143 | 144 | /* 145 | |-------------------------------------------------------------------------- 146 | | HTTP Header Only 147 | |-------------------------------------------------------------------------- 148 | | 149 | | This will tell the resource server where to check for the access_token. 150 | | By default it checks both the query string and the http headers. 151 | | 152 | */ 153 | 154 | 'http_headers_only' => false, 155 | 156 | ]; 157 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Post::class, function (Faker\Generator $faker) { 15 | return [ 16 | 'title' => $faker->sentence(4), 17 | 'content' => $faker->paragraph(4), 18 | 'user_id' => mt_rand(1, 10) 19 | ]; 20 | }); 21 | 22 | $factory->define(App\Comment::class, function (Faker\Generator $faker) { 23 | return [ 24 | 'content' => $faker->paragraph(1), 25 | 'post_id' => mt_rand(1, 50), 26 | 'user_id' => mt_rand(1, 10) 27 | ]; 28 | }); 29 | 30 | $factory->define(App\User::class, function (Faker\Generator $faker) { 31 | 32 | $hasher = app()->make('hash'); 33 | 34 | return [ 35 | 'name' => $faker->name, 36 | 'email' => $faker->email, 37 | 'password' => $hasher->make("secret"), 38 | 'is_admin' => mt_rand(0, 1) 39 | ]; 40 | }); -------------------------------------------------------------------------------- /database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/lumen-api-oauth/75a8a1e70d9b8942631bd4d8f505f750d4d4b573/database/migrations/.gitkeep -------------------------------------------------------------------------------- /database/migrations/2014_04_24_110151_create_oauth_scopes_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth scopes table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthScopesTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_scopes', function (Blueprint $table) { 31 | $table->string('id', 40)->primary(); 32 | $table->string('description'); 33 | 34 | $table->nullableTimestamps(); 35 | }); 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | * 41 | * @return void 42 | */ 43 | public function down() 44 | { 45 | Schema::drop('oauth_scopes'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_110304_create_oauth_grants_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth grants table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthGrantsTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_grants', function (Blueprint $table) { 31 | $table->string('id', 40)->primary(); 32 | $table->nullableTimestamps(); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | * 39 | * @return void 40 | */ 41 | public function down() 42 | { 43 | Schema::drop('oauth_grants'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_110403_create_oauth_grant_scopes_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth grant scopes table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthGrantScopesTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_grant_scopes', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->string('grant_id', 40); 33 | $table->string('scope_id', 40); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->index('grant_id'); 38 | $table->index('scope_id'); 39 | 40 | $table->foreign('grant_id') 41 | ->references('id')->on('oauth_grants') 42 | ->onDelete('cascade'); 43 | 44 | $table->foreign('scope_id') 45 | ->references('id')->on('oauth_scopes') 46 | ->onDelete('cascade'); 47 | }); 48 | } 49 | 50 | /** 51 | * Reverse the migrations. 52 | * 53 | * @return void 54 | */ 55 | public function down() 56 | { 57 | Schema::table('oauth_grant_scopes', function (Blueprint $table) { 58 | $table->dropForeign('oauth_grant_scopes_grant_id_foreign'); 59 | $table->dropForeign('oauth_grant_scopes_scope_id_foreign'); 60 | }); 61 | Schema::drop('oauth_grant_scopes'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_110459_create_oauth_clients_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth client table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthClientsTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_clients', function (BluePrint $table) { 31 | $table->string('id', 40)->primary(); 32 | $table->string('secret', 40); 33 | $table->string('name'); 34 | $table->nullableTimestamps(); 35 | 36 | $table->unique(['id', 'secret']); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | * 43 | * @return void 44 | */ 45 | public function down() 46 | { 47 | Schema::drop('oauth_clients'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_110557_create_oauth_client_endpoints_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth client endpoints table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthClientEndpointsTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_client_endpoints', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->string('client_id', 40); 33 | $table->string('redirect_uri'); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->unique(['client_id', 'redirect_uri']); 38 | 39 | $table->foreign('client_id') 40 | ->references('id')->on('oauth_clients') 41 | ->onDelete('cascade') 42 | ->onUpdate('cascade'); 43 | }); 44 | } 45 | 46 | /** 47 | * Reverse the migrations. 48 | * 49 | * @return void 50 | */ 51 | public function down() 52 | { 53 | Schema::table('oauth_client_endpoints', function (Blueprint $table) { 54 | $table->dropForeign('oauth_client_endpoints_client_id_foreign'); 55 | }); 56 | 57 | Schema::drop('oauth_client_endpoints'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_110705_create_oauth_client_scopes_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth client scopes table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthClientScopesTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_client_scopes', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->string('client_id', 40); 33 | $table->string('scope_id', 40); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->index('client_id'); 38 | $table->index('scope_id'); 39 | 40 | $table->foreign('client_id') 41 | ->references('id')->on('oauth_clients') 42 | ->onDelete('cascade'); 43 | 44 | $table->foreign('scope_id') 45 | ->references('id')->on('oauth_scopes') 46 | ->onDelete('cascade'); 47 | }); 48 | } 49 | 50 | /** 51 | * Reverse the migrations. 52 | * 53 | * @return void 54 | */ 55 | public function down() 56 | { 57 | Schema::table('oauth_client_scopes', function (Blueprint $table) { 58 | $table->dropForeign('oauth_client_scopes_client_id_foreign'); 59 | $table->dropForeign('oauth_client_scopes_scope_id_foreign'); 60 | }); 61 | Schema::drop('oauth_client_scopes'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_110817_create_oauth_client_grants_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth client grants table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthClientGrantsTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_client_grants', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->string('client_id', 40); 33 | $table->string('grant_id', 40); 34 | $table->nullableTimestamps(); 35 | 36 | $table->index('client_id'); 37 | $table->index('grant_id'); 38 | 39 | $table->foreign('client_id') 40 | ->references('id')->on('oauth_clients') 41 | ->onDelete('cascade') 42 | ->onUpdate('no action'); 43 | 44 | $table->foreign('grant_id') 45 | ->references('id')->on('oauth_grants') 46 | ->onDelete('cascade') 47 | ->onUpdate('no action'); 48 | }); 49 | } 50 | 51 | /** 52 | * Reverse the migrations. 53 | * 54 | * @return void 55 | */ 56 | public function down() 57 | { 58 | Schema::table('oauth_client_grants', function (Blueprint $table) { 59 | $table->dropForeign('oauth_client_grants_client_id_foreign'); 60 | $table->dropForeign('oauth_client_grants_grant_id_foreign'); 61 | }); 62 | Schema::drop('oauth_client_grants'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_111002_create_oauth_sessions_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth sessions table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthSessionsTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_sessions', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->string('client_id', 40); 33 | $table->enum('owner_type', ['client', 'user'])->default('user'); 34 | $table->string('owner_id'); 35 | $table->string('client_redirect_uri')->nullable(); 36 | $table->nullableTimestamps(); 37 | 38 | $table->index(['client_id', 'owner_type', 'owner_id']); 39 | 40 | $table->foreign('client_id') 41 | ->references('id')->on('oauth_clients') 42 | ->onDelete('cascade') 43 | ->onUpdate('cascade'); 44 | }); 45 | } 46 | 47 | /** 48 | * Reverse the migrations. 49 | * 50 | * @return void 51 | */ 52 | public function down() 53 | { 54 | Schema::table('oauth_sessions', function (Blueprint $table) { 55 | $table->dropForeign('oauth_sessions_client_id_foreign'); 56 | }); 57 | Schema::drop('oauth_sessions'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_111109_create_oauth_session_scopes_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth session scopes table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthSessionScopesTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_session_scopes', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->integer('session_id')->unsigned(); 33 | $table->string('scope_id', 40); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->index('session_id'); 38 | $table->index('scope_id'); 39 | 40 | $table->foreign('session_id') 41 | ->references('id')->on('oauth_sessions') 42 | ->onDelete('cascade'); 43 | 44 | $table->foreign('scope_id') 45 | ->references('id')->on('oauth_scopes') 46 | ->onDelete('cascade'); 47 | }); 48 | } 49 | 50 | /** 51 | * Reverse the migrations. 52 | * 53 | * @return void 54 | */ 55 | public function down() 56 | { 57 | Schema::table('oauth_session_scopes', function (Blueprint $table) { 58 | $table->dropForeign('oauth_session_scopes_session_id_foreign'); 59 | $table->dropForeign('oauth_session_scopes_scope_id_foreign'); 60 | }); 61 | Schema::drop('oauth_session_scopes'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_111254_create_oauth_auth_codes_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth auth codes table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthAuthCodesTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_auth_codes', function (Blueprint $table) { 31 | $table->string('id', 40)->primary(); 32 | $table->integer('session_id')->unsigned(); 33 | $table->string('redirect_uri'); 34 | $table->integer('expire_time'); 35 | 36 | $table->nullableTimestamps(); 37 | 38 | $table->index('session_id'); 39 | 40 | $table->foreign('session_id') 41 | ->references('id')->on('oauth_sessions') 42 | ->onDelete('cascade'); 43 | }); 44 | } 45 | 46 | /** 47 | * Reverse the migrations. 48 | * 49 | * @return void 50 | */ 51 | public function down() 52 | { 53 | Schema::table('oauth_auth_codes', function (Blueprint $table) { 54 | $table->dropForeign('oauth_auth_codes_session_id_foreign'); 55 | }); 56 | Schema::drop('oauth_auth_codes'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_111403_create_oauth_auth_code_scopes_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth code scopes table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthAuthCodeScopesTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_auth_code_scopes', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->string('auth_code_id', 40); 33 | $table->string('scope_id', 40); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->index('auth_code_id'); 38 | $table->index('scope_id'); 39 | 40 | $table->foreign('auth_code_id') 41 | ->references('id')->on('oauth_auth_codes') 42 | ->onDelete('cascade'); 43 | 44 | $table->foreign('scope_id') 45 | ->references('id')->on('oauth_scopes') 46 | ->onDelete('cascade'); 47 | }); 48 | } 49 | 50 | /** 51 | * Reverse the migrations. 52 | * 53 | * @return void 54 | */ 55 | public function down() 56 | { 57 | Schema::table('oauth_auth_code_scopes', function (Blueprint $table) { 58 | $table->dropForeign('oauth_auth_code_scopes_auth_code_id_foreign'); 59 | $table->dropForeign('oauth_auth_code_scopes_scope_id_foreign'); 60 | }); 61 | Schema::drop('oauth_auth_code_scopes'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_111518_create_oauth_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth access tokens table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthAccessTokensTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_access_tokens', function (Blueprint $table) { 31 | $table->string('id', 40)->primary(); 32 | $table->integer('session_id')->unsigned(); 33 | $table->integer('expire_time'); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->unique(['id', 'session_id']); 38 | $table->index('session_id'); 39 | 40 | $table->foreign('session_id') 41 | ->references('id')->on('oauth_sessions') 42 | ->onDelete('cascade'); 43 | }); 44 | } 45 | 46 | /** 47 | * Reverse the migrations. 48 | * 49 | * @return void 50 | */ 51 | public function down() 52 | { 53 | Schema::table('oauth_access_tokens', function (Blueprint $table) { 54 | $table->dropForeign('oauth_access_tokens_session_id_foreign'); 55 | }); 56 | Schema::drop('oauth_access_tokens'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_111657_create_oauth_access_token_scopes_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth access token scopes table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthAccessTokenScopesTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_access_token_scopes', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->string('access_token_id', 40); 33 | $table->string('scope_id', 40); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->index('access_token_id'); 38 | $table->index('scope_id'); 39 | 40 | $table->foreign('access_token_id') 41 | ->references('id')->on('oauth_access_tokens') 42 | ->onDelete('cascade'); 43 | 44 | $table->foreign('scope_id') 45 | ->references('id')->on('oauth_scopes') 46 | ->onDelete('cascade'); 47 | }); 48 | } 49 | 50 | /** 51 | * Reverse the migrations. 52 | * 53 | * @return void 54 | */ 55 | public function down() 56 | { 57 | Schema::table('oauth_access_token_scopes', function (Blueprint $table) { 58 | $table->dropForeign('oauth_access_token_scopes_scope_id_foreign'); 59 | $table->dropForeign('oauth_access_token_scopes_access_token_id_foreign'); 60 | }); 61 | Schema::drop('oauth_access_token_scopes'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /database/migrations/2014_04_24_111810_create_oauth_refresh_tokens_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * This is the create oauth refresh tokens table migration class. 18 | * 19 | * @author Luca Degasperi 20 | */ 21 | class CreateOauthRefreshTokensTable extends Migration 22 | { 23 | /** 24 | * Run the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function up() 29 | { 30 | Schema::create('oauth_refresh_tokens', function (Blueprint $table) { 31 | $table->string('id', 40)->unique(); 32 | $table->string('access_token_id', 40)->primary(); 33 | $table->integer('expire_time'); 34 | 35 | $table->nullableTimestamps(); 36 | 37 | $table->foreign('access_token_id') 38 | ->references('id')->on('oauth_access_tokens') 39 | ->onDelete('cascade'); 40 | }); 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | * 46 | * @return void 47 | */ 48 | public function down() 49 | { 50 | Schema::table('oauth_refresh_tokens', function (Blueprint $table) { 51 | $table->dropForeign('oauth_refresh_tokens_access_token_id_foreign'); 52 | }); 53 | 54 | Schema::drop('oauth_refresh_tokens'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /database/migrations/2016_03_24_182334_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name'); 18 | $table->string('email')->unique(); 19 | $table->string('password'); 20 | $table->boolean('is_admin'); 21 | $table->nullableTimestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::drop('users'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2016_03_24_221425_create_posts_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('title'); 18 | $table->string('content'); 19 | $table->integer('user_id')->unsigned(); 20 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); 21 | $table->nullableTimestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::drop('posts'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2016_03_24_221457_create_comments_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('content'); 18 | 19 | $table->nullableTimestamps(); 20 | $table->integer('user_id')->unsigned(); 21 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); 22 | 23 | $table->integer('post_id')->unsigned(); 24 | $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade')->onUpdate('cascade'); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('comments'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 27 | factory(Post::class, 50)->create(); 28 | factory(Comment::class, 100)->create(); 29 | 30 | $this->call('OAuthClientSeeder'); 31 | 32 | // Enable it back 33 | DB::statement('SET FOREIGN_KEY_CHECKS = 1'); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /database/seeds/OAuthClientSeeder.php: -------------------------------------------------------------------------------- 1 | truncate(); 15 | 16 | for ($i=0; $i < 10; $i++){ 17 | 18 | DB::table('oauth_clients')->insert( 19 | [ 'id' => "id$i", 20 | 'secret' => "secret$i", 21 | 'name' => "Test Client $i" 22 | ] 23 | ); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | 19 | ./app 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes If Not A Folder... 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteRule ^(.*)/$ /$1 [L,R=301] 11 | 12 | # Handle Front Controller... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_FILENAME} !-f 15 | RewriteRule ^ index.php [L] 16 | 17 | # Handle Authorization Header 18 | RewriteCond %{HTTP:Authorization} . 19 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 20 | 21 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 29 | -------------------------------------------------------------------------------- /public/lumen-api-oauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/lumen-api-oauth/75a8a1e70d9b8942631bd4d8f505f750d4d4b573/public/lumen-api-oauth.png -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/lumen-api-oauth/75a8a1e70d9b8942631bd4d8f505f750d4d4b573/resources/views/.gitkeep -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | get('/', function () use ($app) { 16 | return $app->version(); 17 | }); 18 | 19 | // Posts 20 | $app->get('/posts','PostController@index'); 21 | $app->post('/posts','PostController@store'); 22 | $app->get('/posts/{post_id}','PostController@show'); 23 | $app->put('/posts/{post_id}', 'PostController@update'); 24 | $app->patch('/posts/{post_id}', 'PostController@update'); 25 | $app->delete('/posts/{post_id}', 'PostController@destroy'); 26 | 27 | // Users 28 | $app->get('/users/', 'UserController@index'); 29 | $app->post('/users/', 'UserController@store'); 30 | $app->get('/users/{user_id}', 'UserController@show'); 31 | $app->put('/users/{user_id}', 'UserController@update'); 32 | $app->patch('/users/{user_id}', 'UserController@update'); 33 | $app->delete('/users/{user_id}', 'UserController@destroy'); 34 | 35 | // Comments 36 | $app->get('/comments', 'CommentController@index'); 37 | $app->get('/comments/{comment_id}', 'CommentController@show'); 38 | 39 | // Comment(s) of a post 40 | $app->get('/posts/{post_id}/comments', 'PostCommentController@index'); 41 | $app->post('/posts/{post_id}/comments', 'PostCommentController@store'); 42 | $app->put('/posts/{post_id}/comments/{comment_id}', 'PostCommentController@update'); 43 | $app->patch('/posts/{post_id}/comments/{comment_id}', 'PostCommentController@update'); 44 | $app->delete('/posts/{post_id}/comments/{comment_id}', 'PostCommentController@destroy'); 45 | 46 | // Request an access token 47 | $app->post('/oauth/access_token', function() use ($app){ 48 | return response()->json($app->make('oauth2-server.authorizer')->issueAccessToken()); 49 | }); 50 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $this->assertEquals( 18 | $this->app->version(), $this->response->getContent() 19 | ); 20 | } 21 | 22 | public function testHomePage() 23 | { 24 | 25 | $response = $this->call('GET', '/'); 26 | $this->assertResponseOk(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |