I'm trying to authenticate my single page app (written in Marionette) with my Laravel 5.4 app. In my previous experience, an SPA would:
send the username & password to an /api/login-token endpoint which would return with something like the following:
{
"access_token":"-wt7x8UPRH9GcbTeUQ3wIA7zuc02XeHil4qsoKJcOUU",
"type":"Bearer",
"expires_in":2570,
"refresh_token":"g9zmNkC1zpQ8fM0uSeJdy_VZe7TuBUG4MYGIBuAtXvg"
}
the SPA will store the access_token in the browser's localstorage, and send it in an access_token header with every subsequent request made to the backend
the backend will recognise this access_token and authenticate the user
Unfortunately I'm struggling to understand how to achieve this in Laravel 5.4.
Do I need to use Passport? I really don't think I need OAuth2, but does Passport also offer simple token-based auth? It seems to, and I have gotten the expected tokens described above from the /oauth/token endpoint, but I don't know how to use this token. I don't think it's even for this purpose.
I tried sending a request to /api/user with this token in the headers, POSTing it, and as a query string, with no luck. Also making me concerned is the expires_in from Laravel is a year (31536000 seconds = 365 days) which seems way too long. I'm worried this Passport OAuth token is actually for OAuth2 access and not a 1-day-ish expiring access token that I'm used to.
I also read about Laravel's TokenGuard but that seems like some weird kind of token that's stored in an api_token column on the user table, which is all wrong by my mindset. For example, it doesn't expire, and it's per-user not per-user-session, meaning the same token would have to be used from multiple devices. etc
Very confused... grateful for any help!
You may try JWT and this tutorial might be an inspiration for you: https://scotch.io/tutorials/token-based-authentication-for-angularjs-and-laravel-apps
I actually ended up using Passport, based on some really helpful code found at https://laracasts.com/discuss/channels/code-review/api-authentication-with-passport?page=1&replyId=282168:
routes/api.php
Route::post('auth/token', 'Api\Auth\DefaultController#authenticate');
Route::post('auth/refresh', 'Api\Auth\DefaultController#refreshToken');
app/Http/Controllers/Api/Auth/DefaultController.php
<?php
namespace App\Http\Controllers\Api\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
class DefaultController extends Controller
{
/**
* #var object
*/
private $client;
/**
* DefaultController constructor.
*/
public function __construct()
{
$this->client = DB::table('oauth_clients')->where('id', 2)->first();
}
/**
* #param Request $request
* #return mixed
*/
protected function authenticate(Request $request)
{
$request->request->add([
'username' => $request->username,
'password' => $request->password,
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'scope' => '*'
]);
$proxy = Request::create(
'oauth/token',
'POST'
);
return Route::dispatch($proxy);
}
/**
* #param Request $request
* #return mixed
*/
protected function refreshToken(Request $request)
{
$request->request->add([
'grant_type' => 'refresh_token',
'refresh_token' => $request->refresh_token,
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
]);
$proxy = Request::create(
'/oauth/token',
'POST'
);
return Route::dispatch($proxy);
}
}
Related
When using $this->middleware('auth:auth') the user token can be passed as query string, request payload or bearer token: https://laravel.com/docs/5.8/api-authentication#passing-tokens-in-requests
I want to only allow authentication by bearer token. This means I don't want to allow authentication by query string or post request.
I tried to solve it by adding a middleware which just removes the api_token
public function handle($request, Closure $next)
{
$request->request->set('api_token', null);
return $next($request);
}
but no matter what I do
Route::group(['middleware' => ['auth:api', 'remove-api-token'],
or
Route::group(['middleware' => ['remove-api-token', 'auth:api'],
will always trigger auth:api first. So my approach does not work.
Any ideas how to deny GET/POST authentication for auth:api only?
One option would be to extend the existing tokenGuard and overwrite the getTokenForRequest() method to only accept the bearer token:
Create a new Guard:
"app\Services\Auth\BearerTokenGuard.php"
<?php
namespace App\Services\Auth;
use Illuminate\Auth\TokenGuard;
class BearerTokenGuard extends TokenGuard
{
/**
* Get the token for the current request.
*
* #return string
*/
public function getTokenForRequest()
{
return $this->request->bearerToken();
}
}
Define your new Guard within a service provider:
"app\Providers\AuthServiceProvider.php"
use App\Services\Auth\BearerTokenGuard;
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
Auth::extend('bearerToken', function ($app, $name, array $config) {
return new BearerTokenGuard(Auth::createUserProvider($config['provider']), $app['request']);
});
}
add your new guard driver to the guards config:
"config\auth.php"
'guards' => [
'api' => [
'driver' => 'bearerToken',
'provider' => 'users',
'hash' => false,
],
],
I have not tested this, but it should work, although it might need some modifications.
I am using laravel 6.12 using the Authentication artisan command
php artisan ui vue --auth
I have been following https://laraveldaily.com/auth-login-how-to-check-more-than-just-emailpassword/ website . I would like to know the steps needed to authenticate with a triage i.e company, email address and password.
I am receiving a duplicate when I add company name, it thinks this is the email address. Where do you change this? And I am recieving the following error.
BadMethodCallException
Method App\Http\Controllers\Auth\LoginController::company does not exist.
$credentials = $request->only($this->username(), 'password');
$credentials = array_add($credentials, 'company');
I need to Verify the Company name as part of the authentication process. with email, password & Company. Does anyone know how to authenticate using three authentication credentials?
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
'''
Note: The tutorial you linked shows a bad way of logging in a User using a raw SQL query!. Please use a more standard way.
To login a user using multiple properties do something like this in your Controller method:
public function login(Request $request)
{
//... request validation logic
//take only what is needed to verify login
$credentials = request(['company', 'email', 'password']);
//verify login
if (!Auth::attempt($credentials)) {
return response()->json([
'message' => 'Invalid password'
], 401);
}
//Login user, as you're using Vue I assume you use either JWT or Passport
$user = $request->user();
$tokenResult = $user->createToken('Personal Access Token');
$token = $tokenResult->token;
$token->save();
return response()->json([
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
]);
}
I want to make my API unavailable to every client who doesn't have the token to access.
This means the Android app will send a client as android and token as token string in the header with keys client and token.
Now in middleware, I am checking it with my table fields to pass through authorization. If both matches then I will authorize and if don't then will send 403 response.
I am aware of Passport but it is not what I am looking for. In fact,
consider it as a first layer of security and then use Passport as a
second layer of security to authorize the API
Is this code is correct?
As I am not so familiar with Laravel - Middleware I just want to get some feedback from experts whether the code I have written is accurate and up to the standard. If not, I would appreciate your suggestion and help to make it better.
Middleware
namespace App\Http\Middleware;
use App\ApiToken;
use Closure;
use function response;
class ApiAccess
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
*
* #return mixed
*/
public function handle( $request, Closure $next ) {
if ( $this->checkToken( $request ) ) {
return $next( $request );
}
return response()->json( [ 'error' => 'Unauthorized' ], 403 );
}
public function checkToken( $request ) {
$client = $request->header( 'client' );
$token = $request->header( 'token' );
$checkToken = ApiToken::where( 'client', $client )
->where( 'token', $token )->first();
return $checkToken;
}
}
API Route
I am fetching result from the ApiToken table just to check.
Route::get('/', function(Request $request) {
return ApiToken::all();
})->middleware('apiAccess');
I can't add comment so writing here. It seems right to me but I don't think the extra layer is needed. Maybe your requirement needs it. Anyways, you can improve the checkToken function to return boolean value. Here it is:
public function checkToken( $request ) {
$client = $request->header( 'client' );
$token = $request->header( 'token' );
return ApiToken::where( 'client', $client )
->where( 'token', $token )->exists();
// Nicer, and it will return true and false based on the existence of the token and client.
}
One more thing, status code 403 is returned when the access is forbidden. 401 is returned with unauthorised error.
I made a route in the api file that allow everybody to create users :
Route::post('users', 'UserController#addUser');
When I called it in postman without using request validation it works. But when I created my request file and use it, Laravel return a NotFoundHttpException.
Here's my request file :
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UserAddRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'user_id' => 'required|numeric',
'name' => 'required|string',
'email' => 'required|string',
'password' => 'required|string'
];
}
}
public function addUser(UserAddRequest $request){
$user = new User;
$user->instance_user_id = $request->input('user_id');
$user->name = $request->input('name');
$user->email = $request->input('email');
$user->password = $request->input('password');
$user->save();
}
There is no form because it needs to be send directly to the server with post method. I declared my route in routes/api.php and I call it with the url /api/users The api in that case doesn't need to check credentials.
I solved my problem :
The NotFoundHttpException was raised because I didn't send my parametters correctly and Laravel doesn't where to redirect me back. When I send directly the request to the server there is no url declared for where I'm coming.
I would like to overwrite Laravel 5.2's public function postRegister() in my Auth controller.
I start by changing the route:
Route::post('/auth/register', 'Auth\AuthController#postRegisterI');
In my Auth controller, I now have this new postRegisterI() method as oppose to relying on the foundational postRegister(). But my changes in this new method don't seem to apply?
Is it still pointing to postRegister() for some reason? My AuthController looks like this:
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
public function postRegisterI(Request $request)
{
return print_r($request);
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
//Auth::login($this->create($request->all()));
$this->create($request->all());
//return redirect($this->redirectPath());
return;
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255|unique:users',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:2',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
For example in my ajax requests to /auth/register/ it never echoes out my returns as I indicate and it also keeps trying to redirect me to /home. I'm doing this because instead of passing /auth/register a form, I'm passing it a Javascript array with data to not only create a user (email, password, etc) but also an application that the user needs to join.
Eventually, I would like to create the Application model with that data right when the User is created, but right now I'm not sure that my function is even being called correctly!
EDIT: So, it looks like the __construct() function is causing the issue... should I get rid of it? I don't understand why I need to comment that out to get my returns from postRegisterI()?
First, make sure that your new route is declared before the call to Route::auth(). Otherwise, Laravel will ignore your addition, and will always call the one that Easy Auth comes with.
Second. Why do you want to override a method using another method with a different name? By simply creating a method postRegister in your AuthController you are automatically overriding the method that resides in the Trait. So you shouldn't be worrying about having to add an additional method and route. Unless you want to have a multi-authentication system, and in this case, this wouldn't be the correct solution.
Another thing, for ajax request, make sure you validate that the request is of type ajax:
if ($request->ajax()){
//Code here
return response()->json([],200);
}
This controller is designed to check registration rules, create an user, and redirect to the redirection path. It is not designed for ajax handling, thus, you will need to add code for it
maybe this topic will help you custom register