Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: authentication revamp #8

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/Contracts/JwtSubject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Contracts;

interface JwtSubject
{
/**
* Returns an array of custom claims to be included in a token.
*
* @return array<string, mixed>
*/
public function getClaims(): array;
}
107 changes: 77 additions & 30 deletions app/Http/Controllers/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,137 @@
namespace App\Http\Controllers;

use App\Enum\UserStatus;
use App\Http\Requests\Auth\LogoutRequest;
use App\Http\Requests\Auth\RefreshTokenRequest;
use App\Http\Requests\LoginRequest;
use App\Http\Requests\PasswordResetRequest;
use App\Http\Resources\UserResource;
use App\Services\JwtGuard;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

/**
* Handles authentication stuff.
*/
class AuthController extends Controller
class AuthController extends Controller implements HasMiddleware
{
/**
* Get a JWT via given credentials.
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return [
new Middleware('auth:api', except: ['login', 'refresh']),
];
}

/**
* Get an access token
*
* Upon successful authentication, two tokens will be generated:
*
* - An `access_token` for use with protected API endpoints. Typically short-lived (e.g. 10 minutes).
* - A `refresh_token` which can be used with the `/auth/refresh` endpoint to generate a new `access_token`.
* Typically long-lived (e.g. 1 week).
*
* @return \Illuminate\Http\JsonResponse
* @unauthenticated
*/
public function login(LoginRequest $request)
public function login(LoginRequest $request): JsonResponse
{
$credentials = $request->validated();

if (! $token = auth()->attempt($credentials)) {
return response()->json(['message' => 'Unauthorized'], 401);
if (! $token = $this->auth()->attempt($credentials)) {
return response()->json(['message' => 'Unauthenticated.'], 401);
}

return $this->respondWithToken($token);
}

/**
* Log the user out (Invalidate the token).
* Invalidate a token
*
* @return \Illuminate\Http\JsonResponse
* Optionally send a `refresh_token` to invalidate it too.
*/
public function logout()
public function logout(LogoutRequest $request): JsonResponse
{
auth()->invalidate();
auth()->logout();
$request->validated();

return response()->json(['message' => 'Successfully logged out']);
$this->auth()->invalidate();
if ($request->has(JwtGuard::AUDIENCE_REFRESH)) {
$this->auth()->invalidate($request->input(JwtGuard::AUDIENCE_REFRESH));
}

return response()->json(['message' => 'Successfully logged out.']);
}

/**
* Refresh a token.
* Refresh a token
*
* @return \Illuminate\Http\JsonResponse
* @unauthenticated
*/
public function refresh()
public function refresh(RefreshTokenRequest $request): JsonResponse
{
return $this->respondWithToken(auth()->refresh());
$request->validated();

return $this->respondWithToken($this->auth()->refresh());
}

public function resetPassword(PasswordResetRequest $request, Repository $repository)
/**
* Reset the user password
*/
public function resetPassword(PasswordResetRequest $request, Repository $repository): JsonResponse
{
$data = $request->validated();

$user = auth()->user();

/** @var User */
$user = $this->auth()->user();
$user->forceFill([
'password' => Hash::make($data['password']),
'status' => UserStatus::ACTIVE,
])->setRememberToken(Str::random(60));

]);
$user->save();

event(new PasswordReset($user));

auth()->invalidate();
auth()->logout();
$this->auth()->invalidate();

return response()->json(['message' => 'Password has been reset. You\'ve been logged out.']);
}

/**
* Display the authenticated user
*/
public function whoami(): UserResource
{
/** @var User */
$user = $this->auth()->user();

return new UserResource($user);
}

/**
* Get the token array structure.
*
* @return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken(string $token)
protected function respondWithToken(array $token): JsonResponse
{
[$access_token, $refresh_token] = $token;

return response()->json([
'access_token' => $token,
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60,
]);
}

/**
* @return JwtGuard
*/
protected function auth()
{
return auth();
}
}
16 changes: 3 additions & 13 deletions app/Http/Controllers/NewsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,9 @@ public function store(NewsStoreRequest $request): JsonResponse
* Guests and normal users will only see published news, while a Creator will receive
* a list of all news.
*
* @param int $id
*
* @unauthenticated
*
* @response NewsResource
*/
public function show($id): NewsResource
public function show(string $id): NewsResource
{
$news = $this->find($id, allowGuest: true);

Expand All @@ -108,8 +104,6 @@ public function show($id): NewsResource

/**
* Update the specified resource in storage.
*
* @param int $id
*/
public function update(NewsUpdateRequest $request, $id): JsonResponse
{
Expand All @@ -136,8 +130,6 @@ public function update(NewsUpdateRequest $request, $id): JsonResponse

/**
* Delete the specified resource from storage.
*
* @param int $id
*/
public function destroy(Request $request, $id): Response
{
Expand All @@ -156,10 +148,8 @@ public function destroy(Request $request, $id): Response

/**
* Restore this resource from a deleted state.
*
* @param int $id
*/
public function restore($id): Response
public function restore(string $id): Response
{
$news = $this->find($id, 'restore');

Expand All @@ -173,7 +163,7 @@ public function restore($id): Response
*
* @param int $id
*/
public function upload(UploadImageRequest $request, $id): JsonResponse
public function upload(UploadImageRequest $request, string $id): JsonResponse
{
$news = $this->find($id, 'update');

Expand Down
24 changes: 24 additions & 0 deletions app/Http/Requests/Auth/LogoutRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Http\Requests\Auth;

use App\Rules\Jwt;
use Illuminate\Foundation\Http\FormRequest;

class LogoutRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'refresh_token' => [
'string',
new Jwt,
],
];
}
}
25 changes: 25 additions & 0 deletions app/Http/Requests/Auth/RefreshTokenRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Http\Requests\Auth;

use App\Rules\Jwt;
use Illuminate\Foundation\Http\FormRequest;

class RefreshTokenRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'refresh_token' => [
'required',
'string',
new Jwt,
],
];
}
}
8 changes: 0 additions & 8 deletions app/Http/Requests/LoginRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@

class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
Expand Down
8 changes: 0 additions & 8 deletions app/Http/Requests/PasswordResetRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@

class PasswordResetRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
Expand Down
8 changes: 0 additions & 8 deletions app/Http/Requests/UpdateProfileRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@

class UpdateProfileRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
Expand Down
46 changes: 46 additions & 0 deletions app/Http/Resources/UserResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Http\Resources;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
/**
* The resource that this resource collects.
*
* @var string
*/
public $collects = User::class;

/**
* The "data" wrapper that should be applied.
*
* @var string|null
*/
public static $wrap = null;

/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'status' => $this->status,
'avatar_url' => $this->avatar_url,

/** @var array<string> */
'roles' => $this->roles->pluck('name'),

/** @var array<string> */
'permissions' => $this->getAllPermissions()->pluck('name'),
];
}
}
20 changes: 0 additions & 20 deletions app/JwtCache.php

This file was deleted.

Loading
Loading