3

I'm trying to implement authentication & authorization of users between my microservices and API Gateway.What I have now:

  1. API Gateway which can request to any microservice.
  2. User microservice - where I'm storing all users. laravel/passport implemented to authenticate user in this microservice. Works as it should be, login route returns token which I'm using to authenticate user in this microservice.
  3. Other 5 microservices without any authentication or authorization.

Question is: what is the right way to use authentication & authorization with microservices? I know that I should authenticate users in my API Gateway and authorization will happen inside microservices. But how authorization in other microservices happening if they don't know anything about users? I'm planning to use somehow JWT token with information about user roles but haven't found yet how to put that information into token

3
  • The other 5 microservices operate through the gateway so as long as the gateway verified the user is authenticated then you don't need authentication in the others. You should ensure that the microservices are not directly accessible from the internet though and only via the gateway Commented Jun 10, 2022 at 14:42
  • @apokryfos, yeah i know that. But the biggest problem for me right now is how to authorize user in each resource when the microservices don't know anything about user microservice. Make internal request all the time to user microservice is not the best idea Commented Jun 10, 2022 at 15:03
  • the gateway could be including the authorised user to the request it sends to the microservices e.g. by adding a header with who the user is or something like that Commented Jun 10, 2022 at 15:29

1 Answer 1

6

UPDATE

Below answer is a answer base one communication method, however I'll highly recommend to use other methods to do communication between microservices such as GRPC/RabbitMQ/RPC/etc.

Best Regards

Original Answer

I'll try to explain with a basic example for API.

Let's say you have currently 3 microservices :

  1. Users
  2. Posts
  3. Core

I assume you're using httpOnly cookie to store user token.

In Core microservice I have this route structure:

Route::prefix('core')->group(function () {
    Route::post('register', [AuthController::class, 'register']);
    Route::post('login', [AuthController::class, 'login']);

    Route::middleware('scope.trader')->group(function () {
        Route::get('user', [AuthController::class, 'user']);

    });
});

Now i want to login which i should send an API request, and I should think of a solution to send token anytime I need it.

  1. login(this is where you get token) and register don't need token
  2. user need token (this is where you asked for solution)

So in addition to get a result, I should create a service for user, and here how I've done it :

UserService :
class UserService extends ApiService
{
    public function __construct()
    {
        // Get User Endpoint Microservice API URL
        $this->endpoint = env('USERS_MS') . '/api';
    }
}
ApiService :
abstract class ApiService
{
    protected string $endpoint;

    public function request($method, $path, $data = [])
    {
        $response = $this->getRequest($method, $path, $data);

        if ($response->ok()) {return $response->json();};

        throw new HttpException($response->status(), $response->body());
    }

    public function getRequest($method, $path, $data = [])
    {
        return \Http::acceptJson()->withHeaders([
            'Authorization' =>  'Bearer ' . request()->cookie('token')
        ])->$method("{$this->endpoint}/{$path}", $data);
    }

    public function post($path, $data)
    {
        return $this->request('post', $path, $data);
    }

    public function get($path)
    {
        return $this->request('get', $path);
    }

    public function put($path, $data)
    {
        return $this->request('put', $path, $data);
    }

    public function delete($path)
    {
        return $this->request('delete', $path);
    }
}

If you're wondering where, this UserService come from, then I should say, I've created a package to use it in other microservices, so you can do the same or just create a service and use it in your microservices or etc.

Everything is obvious about ApiService, but I'll try to explain the base.

  1. Anytime we want to do an API call, we can simply call Allowed methods in this class, then our methods, will call request, to pass common arguments, and eventually using those arguments to do the API call.
  2. getRequest method, is doing the call and get the stored token from httpOnly cookie, and will send it as an Authorization header to the target endpoint, and eventually it'll return whatever it get from target.

So If we want to use this, we can simply do like this in our controller :

class AuthController extends Controller
{
    // use Services\UserService;
    public UserService $userService;

    /**
     * @param UserService $userService
     */
    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function register(RegisterRequest $request)
    {
        $data = $request->only('name', 'email', 'password') + ['additional_fileds' => 0 ];
        // additional fields can be used for something except from request and
        // optional, like is it admin or user or etc.

        // call the post method, pass the endpoint url(`register`), pass $data
        $user = $this->userService->post('register', $data);
        // get data from target endpoint
        // and ...
        return response($user, Response::HTTP_CREATED);
    }

    public function login(Request $request)
    {
        // same thing here again, but this time i passed scope to help me
        // get the specific user scope
        $data = $request->only('email', 'password') + ['scope' => 'writer'];

        $response = $this->userService->post('login', $data);
        // as you can see when user do success login, we will get token,
        // which i got that token using Passport and set it to $cookie
        $cookie = cookie('token', $response['token'], 60 * 24); // 1 day
      
        // then will set a new httpOnly token on response.
        return response([
            'message' => 'success'
        ])->withCookie($cookie);
    }

    public function user(Request $request)
    {
        // Here, base on userService as you saw, we passed token in all requests
        // which if token exist, we get the result, since we're expecting
        // token to send back the user informations.

        $user = $this->userService->get('user');

        // get posts belong to authenticated user
        $posts = Post::where('user_id', $user['id'])->get();

        $user['posts'] = $posts;

        return $user;
    }
}

Now, how about user microservice? well Everything is clear here, and it should work like a basic app.

Here's the routes :

Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);

Route::middleware(['bunch','of', 'middlewares'])->group( function (){
    Route::get('user', [AuthController::class, 'user']);
});

And in controller :

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $user = User::create(
            $request->only('first_name', 'email', 'additional_field')
            + ['password' => \Hash::make($request->input('password'))]
        );

        return response($user, Response::HTTP_CREATED);
    }


    public function login(Request $request)
    {
        if (!\Auth::attempt($request->only('email', 'password'))) {
            return response([
                'error' => 'user or pass is wrong or whatever.'
            ], Response::HTTP_UNAUTHORIZED);
        }

        $user = \Auth::user();

        $jwt = $user->createToken('token', [$request->input('here you can pass the required scope like trader as i expalined in top')])->plainTextToken;

        return compact('token');
    }

    public function user(Request $request)
    {
        return $request->user();
    }
}

So here's the complete example and you can use the Core microservice approach on other microservices to get your information related to authenticated user, and as you can see everything will be authenticated due to those requests from core to other microservices.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for example with details! So every time when I need to check is user autorized to use some method in controller I need to make request with UserService to retrieve user? Is it good solution? I'm asked because I've never worked before with microservices, so I'm trying to use best practices
@WorldCG08 Yes. this the best solution currently. when you're doing microservice architecture, one of the cons is a lots of api requests. I've done this with DDD, EDD with CQRS and the platforms are working perfectly fine. but with some knowledge you can reduce those requests base on the project, is it using kafka or rabitmq and etc.
@WorldCG08 And I've wrote this answer because you said how to do it, and here's the real-world example. so that's should be your answer because one way or another, you should do an api call with authorization to authenticate user, not just doing random events and etc.
Thanks! But how to check is user allowed to call the method from controller? Add to user some kind of role field with all available for current user roles and check it in controller method? But as I see you're using scopes in token for that. So I think it should be better solution
Of-course. when you're doing microservice, you're defining what route and actions are available for a microservice, so when you're in need with something specific like role or permission, you can simply use something like scope, to define what are you looking for. for example if user is trader we will pass trade as a scope, if it's admin, we will pass admin as scope. you don't need to over-engineer something, when this approach is flexible and readable.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.