Laravel Passport API registering new users - laravel

I'm trying to build a login-system for my web-app, but I can't get Passport to work. The app is build as a REST API, so users should be able to register with an email and password, and after this they should be able to login with these credentials (so I think they will need to receive an access token from Passport when the login credentials are correct).
I thought I could just do a JSON post to a 'register' route to register a new user and then do a post to a 'login' route to get the access token back to the client, but there is no such thing as far as I can tell.
How do I register a new user?

If you're building SPA and using default Laravel register, login, forgot password, & reset password web functionality like Google Account for authentication & authorization purpose, you can override the registered method on App\Http\Controllers\Auth\RegisterController.php with redirect logic when intended url is exists.
This lines tells Laravel to look over intended url, prior navigation to the web register controller redirect path.
/**
* The user has been registered.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function registered(Request $request, $user)
{
if ($request->session()->has('url.intended')) {
return redirect()->intended();
}
}
For example I'm using authorization code with PKCE grant on my Vue.js SFC
<template>
<v-app-bar app flat>
<button-login #login="authorize"></button-login>
<span>|</span>
<button-register #register="authorize"></button-register>
</v-app-bar>
</template>
<script>
import ButtonLogin from '#/components/Buttons/ButtonLogin'
import ButtonRegister from '#/components/Buttons/ButtonRegister'
import { base64URL, encrypt, hashMake, randomString } from '#/helpers'
import sha256 from 'crypto-js/sha256'
import httpBuildQuery from 'http-build-query'
import { SERVICE } from '#/config/services'
import { STORAGE_API_AUTHORIZATION_STATE, STORAGE_API_CODE_VERIFIER } from '#/config/storage'
export default {
name: 'AppBar',
components: {
ButtonLogin,
ButtonRegister
},
authorize() {
const authorizationState = randomString(40)
const codeVerifier = randomString(128)
const codeChallenge = base64URL(sha256(codeVerifier))
const query = httpBuildQuery({
client_id: SERVICE.CLIENT_ID,
redirect_uri: authorizationURL,
response_type: 'code',
scope: '*',
state: authorizationState,
code_challenge: codeChallenge,
code_challenge_method: 'S256'
})
localStorage.setItem(
STORAGE_API_AUTHORIZATION_STATE,
hashMake(authorizationState)
)
localStorage.setItem(
STORAGE_API_CODE_VERIFIER,
encrypt(codeVerifier, authorizationState)
)
location.href = `${SERVICE.API_URL}/oauth/authorize?${query}`
}
}
</script>
Whenever user click on login/register button on my SPA it'll redirect to my API OAuth authorization page.
The authenticate middleware will intercept the request and check for the logged in state of user, if user is not authenticated then it'll redirect user to the login page.
If user choose to register his/her account by clicking on the register button, we will redirect user to the web registration page (still on API not on SPA).
After the user is registered, the controller will call registered method and check for intended URL existence, if exists then we are able to redirect user to the intended url (the oauth/authorize endpoint), and the authorization process can be continued after registration process.

I'm facing the same problem here, and by now the best solution I've found is to create the register method manually by creating a UserController and a store method like this
public function store(Request $request) {
$data=$request->only('name', 'email','password');
$valid = validator(
$data, [
'name' => 'required|string|max:255',',
'email' => 'required|string|email|max:155|unique:users',
'password' => 'required|string|min:4',
]);
$return=null;
if ($valid->fails()) {
$return = response()->json($valid->errors()->all(), 400);
}else{
$data['password']=Hash::make($data['password']);
#return = User::create($data);
}
return $return;
}

Related

Route type delete does not work in Laravel

I have following route and works
Route::post("delete-role", [RoleApiController::class, "Remove"]);
I tested it through postman like this
http://localhost/delete-role?api_token=hcvhjbhjb12khjbjhc876
Now, if I change above route and convert to type delete..
Route::delete("delete-role/{role_id}", [RoleApiController::class, "Remove"]);
it does not work. I get below error. It seems to be the reason that the api_token is missing.
I get same error when trying to update route like below
Route::delete("delete-role/{role_id}/{api_token}", [RoleApiController::class, "Remove"]);
You have to set header of your request as:
"Accept": "application/json"
in postman.
If you don't set the required header for api, Laravel Passport can't understand request as an API client and so it will redirect to a /login page for the web.
Or you can set a middleware to check it in code:
public function handle($request, Closure $next)
{
if(!in_array($request->headers->get('accept'), ['application/json', 'Application/Json']))
return response()->json(['message' => 'Unauthenticated.'], 401);
return $next($request);
}
You have an incomplete details. but I see few issues here.
You seem to be using web routes for your API requests which is a bad set-up
You do not have a route with login name.
based on the error you posted, your request seems to successfully destroyed the token and logged you out, then called the middleware App\Http\Middleware\Authenticate which supposed to redirect your request to login route which does not exist and the reason you are getting that error.
You can see from that Authenticate middleware it will supposed to redirect you to login route for unauthenticated request. thats why you need to use the api routes so you can handle the response manually
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}
Also, I'm not so sure about this, but the reason you are not getting the same issue with your POST request is probably because your POST request does not call the Authenticate middleware or whatever in your routes file or class function that calls the authenticate middleware.
But again, just use api routes if you don't want un-authenticated request to automatically redirect your request to login routes which does not exist in your application
The problem is that he doesn't define route ('login'),
add in Exceptions/Handler.php
$this->renderable(function (AuthenticationException $e, $request) {
if ($request->is('api/*')) {
return response()->json(['meassge' => 'Unauthenticated']);
}
});
Then you should use Passport Or Sanctum for auth with Api,
Continue from here https://laravel.com/docs/9.x/passport
Probably, this thread could help you. Route [login] not defined
(OR)
You need to setup auth scaffolding to get login route defined.
Important: your project will be overridden if you setup auth scaffold.
So, I would only recommend doing this if it is a new project or a testing app.
See this for detail doc but install Laravel Breeze would be suffice.
It Appears you have called route('login') without having defined it in your routes, Please remove route('login') from your code or define it in your routes. eg::
Route::get('login', [YourController::class, 'methodName'])->name('login');

How to authenticate a user by its user id in Laravel in a custom middleware

How would you guys go about implementing something like this? I use Laravel as an API only. We have other framework where the login was implemented where it saves an httpOnly cookie (sessionId) after the user logs in. That's the main framework. We're migrating away from that old framework (Zend).
With the sessionId sent to Laravel from, say, a JS frontend, I'm able to lookup the current user based on the sessionId. That sessionId is then used to query the session database. I've created a middleware called "CheckForCurrentUser.php":
[..]
public function handle(Request $request, Closure $next)
{
// The reason for this is that the OPTIONS (request) does not include the cookie in the request.
$method = $request->method();
// SESSIONID is the name of the cookie created from the main framework
// once a user is logged in.
// SESSIONID is an exception in EncryptCookies.php
$sessionId = request()->cookie('SESSIONID');
if ($method === 'POST' && $sessionId !== '') {
// This function is only to get the logged in user id from the session database
$userId = $this->notImportantFunction($sessionId);
if ($userId) {
// User id found so make current user for this Laravel API
Auth::loginUsingId($userId);
} else {
// Instructs the frontend to let user log back in.
return abort(401);
}
}
return $next($request);
}
To recap, a user cannot authenticate from this Laravel application nor can I send an authentication token. They logged in from another framework. Laravel has access to the main framework databases.
This setup works. Using use Illuminate\Support\Facades\Auth; I use that as the "current user" in any controller/model.
Kernel.php looks something like:
[..]
protected $middleware = [
[..]
\App\Http\Middleware\CheckCurrentUser::class,
[..]
];
Before I go any further, is that how you'd implement something like this? It does work (ish) but I do not get the SESSIONID unless I check for a POST request.
I do not like this setup. I'm now using lighthouse and having issues using both the #auth and #inject directives. This is due to how I authenticate a use with Laravel so sorting how I authenticate a user should sort Lighthouse. Any tips on how to refactor this the right way? My routes are with /api/some-string
Lighthouse is not the issue. Should I send a authentication header, Lighthouse works. I believe Laravel does something behind the scenes with it sees a token in the header. I cannot send an authentication. I can only rely on the cookie. It's httpOnly so I have no access to that from JavaScript.

email/verify with VusJS SPA and Laravel

I'm currently building a new web application with VueJS SPA, VueJS Router, and Laravel, users should be able to access pages as guests (non-authenticated) or logged-in (authenticated)!
So $this->middleware('auth') is commented from my SpaContoller to give guests access to pages but with some view limitation of course!
I've added basic user authentication by using
php artisan make:auth
and the problem I'm facing is that after registration user gets redirected to the Home page and can access any pages rather than seeing the 'Verify Email' page only!
When I include $this->middleware('auth') for the SpaController it works fine but then guests can't access any pages.
So not sure now to get a proper solution for that?
I`m a beginner in Laravel and Vue-Js. I have done my website with JWT auth. I manage the access to pages using routes as follows.
routes: [
{ path: "/profile", component: profile, meta: { requireAuth: true } },
// this can be access only by registered users
{ path: "/home", component: home }, //this route can be access by anyone
router.beforeEach((to, from, next) => {
//console.log(Store.getters.role);
if (to.meta.requireAuth) {
next();
}
}
);
A Solution but I'm not sure if it would be the best one if you want to force the users to see only 'Verify Email' is to create a middleware and add it to SpaController:
class ForceRedirectToVerifyEmail extends Middleware
{
/**
*
* #param \Illuminate\Http\Request $request
* #return string
*/
protected function redirectTo($request)
{
if (auth()->check() && !auth()->user()->hasVerifiedEmail()) {
return url('verify-email');//Or what ever need to redirect them as normly it would be handled in VueJS or ReactJs themselves.
}
}
}

how to check if user is authenticated with passport (get user from token using laravel-passport)

I am using Passport to log in users to a Laravel API endpoint, users get authenticated using their social accounts (google, facebook) using laravel-socialite package.
the workflow of logging users in and out works perfectly (generating tokens...Etc). The problem is I have a controller that should return data based on whether there is a user logged in or not.
I do intercept the Bearer token from the HTTP request but I couldn't get the user using the token (I would use DB facade to select the user based on the token but I am actually looking whether there is a more clean way already implemented in Passport)
I also don't want to use auth:api middleware as the controller should work and return data even if no user is logged in.
this is the api route:
Route::get("/articles/{tag?}", "ArticleController#get_tagged");
this is the logic I want the controller to have
public function get_tagged($tag = "", Request $request)
{
if ($request->header("Authorization"))
// return data related to the user
else
// return general data
}
Assuming that you set your api guard to passport, you can simply call if (Auth::guard('api')->check()) to check for an authenticated user:
public function get_tagged($tag = "", Request $request)
{
if (Auth::guard('api')->check()) {
// Here you have access to $request->user() method that
// contains the model of the currently authenticated user.
//
// Note that this method should only work if you call it
// after an Auth::check(), because the user is set in the
// request object by the auth component after a successful
// authentication check/retrival
return response()->json($request->user());
}
// alternative method
if (($user = Auth::user()) !== null) {
// Here you have your authenticated user model
return response()->json($user);
}
// return general data
return response('Unauthenticated user');
}
This would trigger the Laravel authentication checks in the same way as auth:api guard, but won't redirect the user away. In fact, the redirection is done by the Authenticate middleware (stored in vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php) upon the failure of the authentication checking.
Beware that if you don't specify the guard to use, Laravel will use the default guard setting in the config/auth.php file (usually set to web on a fresh Laravel installation).
If you prefer to stick with the Auth facade/class you can as well use Auth::guard('api')->user() instead or the request object.
thanks to #mdexp answer
In my case I can resolve my problem with using
if (Auth::guard('api')->check()) {
$user = Auth::guard('api')->user();
}
In my controller.

Using laravel socialite and jwt-auth without session

Short version: What would be the appropriate way to send the JWT generated from Facebook login (laravel/socialite) to the angularjs front end without using session.
Long Version
I am making an app that has angularjs front end and laravel 5.2 backend. I am using tymondesigns/jwt-auth for authentication instead of session.
I am also using laravel/socialite for social Facebook authentication. For that I am using the stateless feature of socialite so that I don't need session in any ways.
The basic authentication works perfectly. But, when I try to use Facebook login, I follow these steps
User clicks on a button on the angular side that redirects to the provider login page of the back end.
public function redirectToProvider() {
return Socialite::with('facebook')->stateless()->redirect();
}
2. User gives his login information. After logging in he is redirected to my handlecallback function.
try {
$provider = Socialite::with('facebook');
if ($request->has('code')) {
$user = $provider->stateless()->user();
}
} catch (Exception $e) {
return redirect('auth/facebook');
}
return $this->findOrCreateUser($user);
Next I use the findorcreate function to determine whether the user exists or not. If not than I just create a new user and create JWT from that.
$user = User::where('social_id', '=', $facebookUser->id)->first();
if (is_object($user)) {
$token = JWTAuth::fromUser($user);
return redirect()->to('http://localhost:9000/#/profile?' . 'token=' . $token);#angular
} else {
$result = array();
$result['name'] = $facebookUser->user['first_name']
$result['email'] = $facebookUser->user['email'];
$result['social_id'] = $facebookUser->id;
$result['avatar'] = $facebookUser->avatar;
$result['gender'] = $facebookUser->user['gender'];
$result['status'] = 'active';
$result['login_type'] = 'facebook';
$result['user_type'] = 'free_user';
try {
$user = User::create($result);
} catch (Exception $e) {
return response()->json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT);
}
$token = JWTAuth::fromUser($user);
return redirect()->to('http://localhost:9000/#/profile?' . 'token=' . $token);#angular
}
My problem is, in the last block of code I am having to send the jwt to my frontend via url. Which isn't secure at all. What would be the right way to send the generated JWT to the frontend without using session. Thank you
The official documentation of Laravel Socialite says:
Stateless Authentication
The stateless method may be used to disable session state verification. This is useful when adding social authentication to an API:
return Socialite::driver('google')->stateless()->user();
Then, you can authenticate using the jwt-auth method:
JWTAuth::fromUser($user)
If you're using $http on the Angular side, try returning the token as a JSON response from Laravel:
return response()->json(compact('token'));
Then store the token in localStorage or sessionStorage or what have you.
If you're generating your Angular page from within Laravel (i.e. not using Laravel as an API, but showing your Angular page from /public/index.php, for instance) you could load the view with the token in the data for the view.
As long as you're using HTTPS either of these two scenarios are better than passing the token in the redirect URL.
You can store token and use client side redirect without storing to browser history to redirect user to profile page without token in URL:
document.location.replace({profile-url})

Resources