Skip to content

Commit 49589a2

Browse files
committed
🚀 Oauth2 between microservices with authorization server
0 parents  commit 49589a2

8 files changed

+296
-0
lines changed

Diff for: .editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 4
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false
13+

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor/
2+
composer.lock

Diff for: README.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# OAuth2 between Laravel projects
2+
3+
A package that allows secure communication between two or more projects, focused mainly for use in microservices architectures, adding the Oauth2 authorization standard in addition to security at the network level by IP addresses and whitelists, which may already be owned.
4+
5+
## Features
6+
7+
- Simple implementation
8+
- It does not increase the latency of requests between microservices.
9+
- High level of security
10+
11+
## Prerequisites
12+
13+
1. Having an authorization server, it is recommended to use [Laravel Passport](https://laravel.com/docs/8.x/passport#client-credentials-grant-tokens) for this, specifically in the [Client Credentials Grant Tokens](https://laravel.com/docs/8.x/passport#client-credentials-grant-tokens) section
14+
15+
2. Store the file `oauth-public.key` at folder `storage/app/` in the microservices to communicate, this file is provided by the authorization server
16+
17+
## Installation
18+
19+
1. Import the library
20+
```
21+
composer require diimolabs/laravel-oauth2-client
22+
```
23+
24+
2. Add the following environment variables:
25+
```
26+
OAUTH_HOST=
27+
OAUTH_CLIENT_ID=
28+
OAUTH_CLIENT_SECRET=
29+
```
30+
And fill with the data provided by the authorization server when creating the client corresponding to the project
31+
32+
3. Implement the `middleware` that validates the authorization of the input requests, in the file `app/Http/kernel.php`
33+
```php
34+
protected $routeMiddleware = [
35+
// Other middleware...
36+
'jwt' => \Diimolabs\Oauth2Client\Middleware\EnsureJwtIsValidMiddleware::class
37+
];
38+
```
39+
40+
## Use
41+
42+
Example of requesting a resource to a microservice
43+
44+
```php
45+
use Diimolabs\Oauth2Client\OAuthHttpClient;
46+
47+
Route::prefix('v1')->group(function(){
48+
Route::get('message', function(){
49+
return OAuthHttpClient::oauthRequest()
50+
->get('http://msa-2.test/api/v1/hello-world')
51+
->body();
52+
});
53+
});
54+
55+
```
56+
57+
Example of a request from a microservice client
58+
59+
```php
60+
Route::prefix('v1')->middleware('jwt')->group(function ()
61+
{
62+
Route::get('/hello-world', function ()
63+
{
64+
return 'Hello world from microservice 2';
65+
});
66+
});
67+
```

Diff for: composer.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "diimolabs/laravel-oauth2-client",
3+
"type": "library",
4+
"description": "Package to handle communication with authorization between microservices",
5+
"keywords": [
6+
"laravel",
7+
"microservices",
8+
"authorization",
9+
"oauth"
10+
],
11+
"license": "MIT",
12+
"authors": [
13+
{
14+
"name": "Javier Villatoro",
15+
"email": "[email protected]"
16+
}
17+
],
18+
"require": {
19+
"php": ">=7.4",
20+
"firebase/php-jwt": "^5.4",
21+
"guzzlehttp/guzzle": "^7.3"
22+
},
23+
"autoload": {
24+
"psr-4": {
25+
"Diimolabs\\Oauth2Client\\": "src/"
26+
}
27+
},
28+
"extra": {
29+
"laravel": {
30+
"providers": [
31+
"Diimolabs\\Oauth2Client\\OAuthServiceProvider"
32+
]
33+
}
34+
}
35+
}

Diff for: src/Middleware/EnsureJwtIsValidMiddleware.php

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Diimolabs\Oauth2Client\Middleware;
4+
5+
use Closure;
6+
use Firebase\JWT\JWT;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Support\Facades\Log;
9+
10+
class EnsureJwtIsValidMiddleware
11+
{
12+
/**
13+
* Handle an incoming request.
14+
*
15+
* @param \Illuminate\Http\Request $request
16+
* @param \Closure $next
17+
* @param string $scopes
18+
* @return mixed
19+
*/
20+
public function handle(Request $request, Closure $next, $scopes = "")
21+
{
22+
try {
23+
if (! $request->bearerToken()){
24+
return response()->json([
25+
'message' => 'You are not authorized'
26+
], 401);
27+
}
28+
29+
$authorizationHeader = $request->header('Authorization');
30+
$jwt = explode(' ', $authorizationHeader)[1];
31+
32+
$publicKey = file_get_contents(storage_path('app/oauth-public.key'));
33+
34+
JWT::decode($jwt, $publicKey, ['RS256']);
35+
} catch (\Throwable $th) {
36+
Log::error("[JWT Valid Error]: " . $th->getMessage() . '. On line:' . $th->getLine());
37+
38+
return response('Internal server error', 500);
39+
}
40+
41+
return $next($request);
42+
}
43+
}

Diff for: src/OAuthHttpClient.php

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace Diimolabs\Oauth2Client;
4+
5+
use Illuminate\Support\Facades\Http;
6+
use Illuminate\Support\Facades\Storage;
7+
8+
class OAuthHttpClient
9+
{
10+
private static $path;
11+
12+
private static $host;
13+
14+
private static $clientId;
15+
16+
private static $clientSecret;
17+
18+
private static function getCredentialsInfo() {
19+
return collect(json_decode(
20+
file_get_contents(self::$path)
21+
));
22+
}
23+
24+
private static function requestToken() {
25+
$response = Http::acceptJson()
26+
->post(self::$host . '/oauth/token', [
27+
'grant_type' => 'client_credentials',
28+
'client_id' => self::$clientId,
29+
'client_secret' => self::$clientSecret,
30+
]);
31+
32+
if($response->failed()){
33+
abort(403);
34+
}
35+
36+
Storage::put('oauth-credentials.json.key', $response->body());
37+
}
38+
39+
private static function validateToken() {
40+
// check if exist file with token
41+
if(!file_exists(self::$path)){
42+
self::requestToken();
43+
}
44+
45+
$data = self::getCredentialsInfo();
46+
47+
$expiredDate = now()->parse(date("Y-m-d H:i:s", $data['expires_in']));
48+
49+
if(now()->lt($expiredDate)){
50+
self::requestToken();
51+
}
52+
}
53+
54+
private static function getAuthorizationToken() {
55+
self::validateToken();
56+
57+
return self::getCredentialsInfo()['access_token'];
58+
}
59+
60+
private static function getOauthAuthorization(){
61+
return [
62+
'Authorization' => 'Bearer ' . self::getAuthorizationToken()
63+
];
64+
}
65+
66+
public static function oauthRequest() {
67+
self::$path = storage_path('app/oauth-credentials.json.key');
68+
self::$host = config('oauth-client.host');
69+
self::$clientId = config('oauth-client.client_id');
70+
self::$clientSecret = config('oauth-client.client_secret');
71+
72+
info(self::$path);
73+
return Http::withHeaders(
74+
self::getOauthAuthorization()
75+
);
76+
}
77+
}

Diff for: src/OAuthServiceProvider.php

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Diimolabs\Oauth2Client;
4+
5+
use Diimolabs\Oauth2Client\OAuthHttpClient;
6+
use Illuminate\Support\ServiceProvider;
7+
8+
class OAuthServiceProvider extends ServiceProvider
9+
{
10+
public function boot()
11+
{
12+
$this->publishes([
13+
__DIR__ . '/config/oauth-client.php' => config_path('oauth-client.php')
14+
], 'oauth-client');
15+
}
16+
17+
public function register()
18+
{
19+
$this->app->singleton(OAuthHttpClient::class, function(){
20+
return new OAuthHttpClient();
21+
});
22+
}
23+
}

Diff for: src/config/oauth-client.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
return [
4+
5+
/*
6+
|--------------------------------------------------------------------------
7+
| OAuth2 Credentials information
8+
|--------------------------------------------------------------------------
9+
|
10+
| Information required to request tokens from the authorization server
11+
|
12+
*/
13+
14+
'host' => env('OAUTH_HOST', ''),
15+
16+
'client_id' => env('OAUTH_CLIENT_ID', ''),
17+
18+
'client_secret' => env('OAUTH_CLIENT_SECRET', ''),
19+
20+
/*
21+
|--------------------------------------------------------------------------
22+
| External services
23+
|--------------------------------------------------------------------------
24+
|
25+
| A key => value pair array that consists on the different external services
26+
| that the resource server or application wants to communicate in which can
27+
| be called . E.g:
28+
| [ 'service1' => 'https://api.service1.com/v1' ]
29+
| So, you can use it within your preferred http client package like this:
30+
| $this->http->post(config('oauth-client.services.service1') . '/blog')
31+
|
32+
*/
33+
'external_services' => [
34+
// "example" => "https://myservice.com/api"
35+
],
36+
];

0 commit comments

Comments
 (0)