Mike_NotGuilty
Mike_NotGuilty

Reputation: 2405

Security - Laravel passport authentication with first party ReactJS Frontend and React-Native app

I've created a Laravel backend with an API. In addition, I added Laravel passport for authentication.

Right now I want to access the API with my ReactJS Frontend application and with a React-Native application. The ReactJS application is on the same server as the Laravel backend.

Now I'm looking for the best way to connect everything with the best security.

I've been looking around and checking tutorials, HowTos, ... for over a week now and I don't know what is best.

Right now my "setup" is, because of some threads and stuff, like this:

I checked the Laravel documentation and several HowTos and implemented the Password Grant Client in my application:

public function login(Request $request){

      $http = new GuzzleHttp\Client;

      $response = $http->post(env('APP_URL') . '/oauth/token', [
        'form_params' => [
          'grant_type' => 'password',
          'client_id' => env('PASSWORD_CLIENT_ID'),
          'client_secret' => env('PASSWORD_CLIENT_SECRET'),
          'username' => $request->input('email'),
          'password' => $request->input('password'),
          'scope' => '',
        ],
      ]);

      $responseJSON = json_decode($response->getBody(), true);
      $output = array(
        'access_token' => $responseJSON['access_token'],
        'expires_in' => $responseJSON['expires_in']
      );

      return response($output)
                ->header('Content-Type', 'application/json')
                ->cookie('refreshToken', $responseJSON['refresh_token'], 14400, null, null, true, true);
    }

and for refreshing the token:

public function tryRefresh(Request $request) {
      $http = new GuzzleHttp\Client;

      $refreshToken = $request->cookie('refreshToken');

      $response = $http->post(env('APP_URL') . '/oauth/token', [
        'form_params' => [
        'grant_type' => 'refresh_token',
        'refresh_token' => $refreshToken,
        'client_id' => env('PASSWORD_CLIENT_ID'),
        'client_secret' => env('PASSWORD_CLIENT_SECRET'),
        'scope' => '',
        ],
      ]);

      $responseJSON = json_decode($response->getBody(), true);
      $output = array(
        'access_token' => $responseJSON['access_token'],
        'expires_in' => $responseJSON['expires_in']
      );

      return response($output)
                ->header('Content-Type', 'application/json')
                ->cookie('refreshToken', $responseJSON['refresh_token'], 14400, null, null, true, true);
    }

Access Token is set to 30 min and refresh token to 10 days. Everything works but I don't know if this is a good practice because:

So my question is: What is a good/best practice for my kind of use case, so the authentication is secure and working on both (ReactJs Web-App and React-Native Mobile App).

Upvotes: 1

Views: 2948

Answers (2)

darki73
darki73

Reputation: 1127

The idea is to do as follows:

  1. Create token on the backend
  2. Send token to client on login (in my Vue.js application expiration is set to 1 year) and save it in cookies with secure option, not the httpOnly one (this will never pass the cookie value to the client)
  3. Pass the token as Authorization header to the backend from your client, i'm using axios, so it is as simple as request.headers.common['Authorization'] = `Bearer ${token}`;
  4. Implement refresh token logic on the backend, no need to store it on the client side, basically whenever your main token will expire, the next request to the server with expired token should either refresh it, or redirect user back to login page (i've chosen the second approach to eliminate possible issues with bad logic on the server side)

Here are the examples of my log in and log out methods:

/**
 * Authenticate user.
 *
 * @param Request $request
 *
 * @return JsonResponse
 */
public function authenticate(Request $request): JsonResponse
{

    $validator = Validator::make($request->all(), [
        'email' => 'required|string|email',
        'password' => 'required|string|min:8',
        'remember_me' => 'boolean',
    ]);

    if ($validator->fails()) {
        return $this->sendError('errors.invalid_request', $validator->errors()->toArray(), Response::HTTP_BAD_REQUEST);
    }

    $credentials = request(['email', 'password']);

    if (!\Auth::attempt($credentials)) {
        return $this->sendError('errors.account.invalid_credentials', [], Response::HTTP_FORBIDDEN);
    }

    $user = $request->user();
    if (!$user->active) {
        return $this->sendError('errors.account.inactive', [], Response::HTTP_CONFLICT);
    } elseif (null !== $user->deleted_at) {
        return $this->sendError('errors.account.deleted', [], Response::HTTP_CONFLICT);
    }

    $createdToken = $user->createToken(sprintf('Access token for %s', $user->username));
    $token = $createdToken->token;

    if ($request->remember_me) {
        $token->expires_at = now()->addYears(1);
    }

    $token->save();

    return $this->sendResponse('account.status.authenticated', [
        'access_token' => $createdToken->accessToken,
        'token_type' => 'Bearer',
        'expires_at' => Carbon::parse($createdToken->token->expires_at)->toDateTimeString(),
    ]);
}

/**
 * Logout user and revoke access token.
 *
 * @param Request $request
 *
 * @return JsonResponse
 */
public function logout(Request $request): JsonResponse
{
    $request->user()->token()->revoke();

    return $this->sendResponse('account.status.logged_out');
}

Upvotes: 0

Angad Dubey
Angad Dubey

Reputation: 5452

Given the frameworks you want to use, the token based authentication you have implemented IS good practice.

You can also look into JWT as an alternative to Password Grant type authentication.

Upvotes: 1

Related Questions