Laravel sanctum API, retrieve the token for use in view components - laravel

Here is the context. I have two sites using the same domain.
The first using Laravel and view components The second is an "API", I use Laravel Sanctum. This API has a single user. Later, there will be a third site using this same API as well.
The API authentication system works perfectly. When I switch from Postman my user, my token is returned.
I'm wondering about token retrieval and usage on my first site using vue components.
My idea was to store API user login credentials in my .env file and retrieve the token in controllers where I use vue components.
Another solution would be to fetch my token on each call to the API, but that could be cumbersome.
Finally store the token at the user's connection and reuse it but where to store it, in session, in cookies,... Security level is not ideal.
Thanks in advance for your ideas.

The default thing to do to use token as a bearer token from the front end is as following
login attempt to backend and you will get the token to authenticate your request later.
store it using vuex store in you user store
then do your API request using that token
destroy the token after logout
if you are concerning about security, well the token should have expiration time and the token itself should be destroyed from the backend when the user is logged out. Every time the user do the login, new token will be created for them

If your API authentication is working properly, Then you've to store your token on the database every time the user logs in. And delete on some other occasions. To store your token on database every time the user logs in use the following code:
public function login(Request $request)
{
if (!Auth::attempt($request->only('email', 'password'))) {
return response()->json([
'message' => 'Invalid credential'
], 401);
}
$user = User::where('email', $request['email'])->firstOrFail();
//store the hashed token on db and return plain text token for the user
$token = $user->createToken('token_name')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
I took the code from here

Related

Laravel Jetstream/Sanctum API authentication

I have been working with Laravel since version 5.X up to version 8.X but always use it for backend API (never used blade template), and always pair it with VueJS on the front-end using JWT authentication (also never messed with any other authentication method).
Now with Laravel 9 and Vue 3, Im trying to use native Laravel Jetstream that uses SANCTUM and Vue+Inertia JS, and I'm quite lost with the authentication process. with JWT method, once the user succesfully login on the browser, all api request to Laravel will be authenticated using Authoraziation header. but this seems a different case with Sanctum.
After deploying and installing Jetstream and completed all the set-up. I created a user and loggedin with that user details. and I notice few things, there is a default API route
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
when I tried to directly access my.domain/api/user I notice it was redirected to GET /login
then redirected again to GET /dashboard
I then created a test api route using below
Route::get('test', function( Request $req) {
dd( [
'test' => $req->all(),
'user' => auth()->user(),
'request' => $req
] );
});
and I notice this request is not authenticated even when the cookies is present on the request as Im when I'm alraedy logged-in on the same browser, the auth()->user() is null.
I tried adding auth:sanctum middleware
Route::middleware('auth:sanctum')->get('test', function( Request $req) {
dd( [
'test' => $req->all(),
'user' => auth()->user(),
'request' => $req
] );
});
but having sanctum middle behave the same as the api/user where if i open api/test directly on the browser, it gets redirected to GET /login then redirected again to GET /dashboard and I'm quite lost at this point. I tried reading the docs and it says I have to do a separate authentication for this that would issue an API token and I was thinking I might better be going back with using JWT auth as it seems a lot easier to deal with.
So my question is; How can I authenticate an API end-point without having to redirect it to /login then /dashboard if the user is already logged in on my application using default sanctum authentication.
My goal is just to simply create /api/test that will be automatically authenticated if user already loggedin on the same browser and return the data I set on its return value and not doing any redirects.
Appreciate any help
I have got the same issue with laravel8
Jetstream and inertia vue3.
Am looking for the solution since 3 days posting messages on discord, searching on YouTube and more but nothing.
When i make an api call from your SPA to laravel, i got UNAUTHENTICATED response.
on postman you need put
headers
Accept = application/json
this tells your application know how works with Json
and go stop redirect to "Login"

Should we make a request to /sanctum/csrf-cookie first, before registration that logs in a user after a successful registration?

My SPA is in the same repository as my laravel application and the documentation states that when using sanctum, your SPA's "login" page should first make a request to the /sanctum/csrf-cookie endpoint to initialize CSRF protection for the application.
Link: https://laravel.com/docs/9.x/sanctum#spa-authenticating
In my case, When I register a user successfully I'm not redirecting them to the login page to log in but rather to their dashboard. So, in my understanding going by the point above, I think I should first make a request to the /sanctum/csrf-cookie endpoint as well before making a post request to the register api so that we have a logged-in user that is protected from CSRF attacks but I'm not that sure if I'm interpreting the text correctly.
my method
public function register(Request $request){
$fields = $request->validate([
'name' => 'required',
'email' => 'required|email|unique:users,email',
'password' => 'required|confirmed',
]);
$fields['password'] = bcrypt($fields['password']);
$user = User::create($fields);
auth()->login($user);
}
I investigated this issue further and found that the /sanctum/csrf-cookie endpoint actually only returns a 204 empty content. You can check here:
https://github.com/laravel/sanctum/blob/5a602d520474e103174900301d7b791e6d7cd953/src/Http/Controllers/CsrfCookieController.php#L12
return new JsonResponse(null, 204);
And the comment in the file says:
Return an empty response simply to trigger the storage of the CSRF cookie in the browser.
Actually you can call any GET API endpoint from your SPA and it will return the CSRF cookie. Basically you just need to have called a GET endpoint once before calling any POST endpoint, including the login endpoint, which should be POST.
The reason for this is that sanctum by default returns a CSRF cookie when you call a GET endpoint for SPAs (using same host or same sub-host).
So for most use cases out there you might not need to call the /sanctum/csrf-cookie endpoint before login, because you might have already called a GET endpoint before that. However if the login, or any other POST endpoint is the first one you are calling, you first need to call the above GET endpoint just to trigger the storage of the CSRF cookie in the browser.
The docs are not so clear on this I am trying to submit a PR to clear this up.

Magic Link login with Laravel Sanctum

For my project I have a set of users that should only be able to login by requesting a Magic Link. So they have an email address but no password. To avoid security issues, my goal was to get this working without having to save an authentication token in LocalStorage.
I've tried setting this up the following way with Laravel Sanctum:
When requested, I create a token for the user and email them the plaintext version.
The user would open the link containing the token in the querystring.
I would attach the (Bearer) token with the Authorization Header.
The next step (I assumed) would be to call a custom /api/login endpoint that uses the 'auth:sanctum' middleware. The Bearer token would authenticate the user and then I would manually login the user with Auth::login(). After this the active Session would be used to authenticate the user, thus avoiding having to save the token in localStorage.
But I can't call the Auth::login() method manually without getting an error (BadMethodCallException: Method Illuminate\Auth\RequestGuard::login does not exist.).
I can't figure out why this isn't working, or maybe I am going at this all wrong?
if you sending Sanctum token to user via email so in 1st request you will get token from url and you can use that token to login to application like this
use Laravel\Sanctum\PersonalAccessToken;
public function login(Request $request)
{
$personalAccessToken = PersonalAccessToken::findToken($request->token);
$user = $personalAccessToken->tokenable;
auth()->login($user);
return redirect('/');
}

Laravel combine Passport authentication and normal authentication

How do I combine Passport authentication and normal laravel authentication?
I want the user to be logged in on pages of web-middleware and api-middleware. The login route is in api-middleware. Currently I have set up Passport authentication and it works fine for all api-middleware routes. How to make the user logged in in web-middleware as well?
Edit #1
What Im doing:
Login code
$http = new \GuzzleHttp\Client();
try {
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => config('services.passport.client_id'),
'client_secret' => config('services.passport.client_secret'),
'username' => $args['email'],
'password' => $args['password']
]
]);
$user = User::where('email', $args['email'])->first();
Auth::guard('web')->login($user);
return [
"token" => $response->getBody()->getContents(),
"user" => $user
];
} // ...
Somewhere in some web-middleware route
return auth()->check() ? "logged in" : "not logged in";
returns "not logged in"
Ideally you shouldn't, as passport auth is for a separate app communicating to the API and laravel preshipped auth is for MVC, they are separate user sessions.
But assuming you know what you are doing, either call Auth::login($user); on user login via API, or generate the passport token when the user login through web middleware auth, whichever login happens first...
Remember Auth::login($user); creates a user session and sets cookies to refer to that session... So you create for yourself a new problem were on logout, there are two places to logout from... as technically the user is logged in twice, with passport token and with a cookie referring to his session...
Actually I'm in a situation like you were. I have searched a lot about it. I always needed web authentication because of nature of my projects but in addition I started to develop projects with api backend soo late in comparing with web development world.
I'm a bit lazy so I generally use Laravel Passport and without working always out of the box, it does the job so in my opinion if you want just the functionality of access tokens for api security, put your user login authentication on web side and just authenticate the api endpoints with auth:api middleware in your api.php file.
I know that that's not the best practice but since it sounds that you are not developing a pure Laravel SPA then you can follow the route for Laravel Multipage application with Vue powered.
But believe me best way is to use either web authentication or api authentication not together then as the above answer says, you will have two authentication working at the same time which does not sound ok.
At the end, when you want to get the authenticated user on blade templates you will use
auth()->user()
but on the other hand in api controllers you should use
auth('api')->user()
which is nice to have but dangerouse to use.
If you need to log an existing user instance into your application, you may call the login method with the user instance.
Auth::login($user);
You can also use the guard() method:
Auth::guard('web')->login($user);
See the documentation here for more information: https://laravel.com/docs/5.8/authentication#authenticating-users

Using client credentials middleware for all API requests

In my routes/api.php file, I have a route group like this:
Route::group([
'prefix' => config('api.route_prefix'),
'middleware' => ['api', 'auth:api'],
], function() {
// ...
This correctly only allows users with tokens retrieved via password grant access to those routes. When trying to implement client credentials grant, I found that a separate middleware is necessary. Since the auth:api middleware raises an exception, this presents a conflict because I want requests with valid tokens of either grant type to access these routes.
What I found is that using just the client credential middleware seems to validate both, but I am unsure if there are any bad implications of doing so.
Is there anything wrong with circumventing the auth:api middleware and replacing it with Laravel\Passport\Http\Middleware\CheckClientCredentials?
One apparent big downside is that the client credentials doesn't seem to have any user information in the JWT token. This causes the user resolver for the request to return null for calls to request()->user(). From Laravel\Passport\Guards\TokenGuard::authenticateViaBearerToken, this was returning null:
// If the access token is valid we will retrieve the user according to the user ID
// associated with the token. We will use the provider implementation which may
// be used to retrieve users from Eloquent. Next, we'll be ready to continue.
$user = $this->provider->retrieveById(
$psr->getAttribute('oauth_user_id')
);
Tracing $psr->getAttribute led me to League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator::validateAuthorization:
// Return the request with additional attributes
return $request
->withAttribute('oauth_access_token_id', $token->getClaim('jti'))
->withAttribute('oauth_client_id', $token->getClaim('aud'))
->withAttribute('oauth_user_id', $token->getClaim('sub'))
->withAttribute('oauth_scopes', $token->getClaim('scopes'));
All of the attributes except oauth_user_id were being set correctly via the claims on the token, $token in my case is an instance of Lcobucci\JWT\Token. So only using the client credentials middleware is not a good solution to having a single set of routes, even if using an oauth client with a specified user_id.

Resources