Apparently, am able to consume my own API via Javascript using VueJs' Axios and jQuery's Ajax but the same has failed to work with Guzzle HTTP client.
How can I use the CreateFreshApiToken middleware with Guzzle.
Axios - Ok
axios.get('api/user').then(response => {
console.log(response.data);
}).catch(error => {
console.log(error.response.data);
});
Ajax - OK
window.$.ajax({
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
type: 'GET',
url: 'api/user',
success: function (response) {
console.log(response);
},
error: function (xhr) {
console.error(xhr.responseText);
}
});
Guzzle - Failed
try {
$client = new Client([
'base_uri' => 'http://localhost/passport-test/public/api/',
'headers' => [
'Accept' => 'application/json',
],
]);
$api_response = $client->request('GET', 'user', ['debug' => true]);
$user = json_decode($api_response->getBody(), true);
return response()->json($user);
} catch (ConnectException $ex) {
return response()->json(['code' => $ex->getCode(), 'message' => $ex->getMessage()]);
} catch (ClientException $ex) {
return json_decode($ex->getResponse()->getBody(), true);
} catch (ServerException $ex) {
return response()->json(['code' => $ex->getCode(), 'message' => $ex->getMessage()]);
}
Well, createFreshApiTokens attaches a cookie for authorization, but when you are using guzzle you are not making a request from the client side ( browser ) so the cookie is not attached to the request!
Related
Authenticating with the sanctum token is working just fine but sadly I can't get it to work using cookies instead (calling /api/user returns 401 Unauthorized). I'd really like to make use of the cookie authentication because it's safer.
Does anyone has an idea of what I'm doing wrong (or missing) to make the authentication work based on cookie so the frontend doesn't have to send sanctums bearer token?
Setup
I'm running React outside of Laravel but both the laravel backend and the react frontend are running on the same domain under subdomains (api.website.com and customer.website.com).
.env
SESSION_DRIVER=cookie
SESSION_DOMAIN=.website.com
SESSION_LIFETIME=120
SESSION_SECURE_COOKIE=false
SANCTUM_STATEFUL_DOMAINS=localhost:8000,.website.com
kernel.php
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
session.php
'same_site' => 'lax',
'http_only' => true,
'driver' => env('SESSION_DRIVER', 'file'),
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:3000', 'https://api.website.com', 'https://customer.website.com'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['x-csrf-token', 'x-xsrf-token', 'content-type'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
UserController.php
public function login(Request $request)
{
if (!$request->email) {
return response()->json(['error' => 'Missing email'], 401);
} elseif (!$request->password) {
return response()->json(['error' => 'Missing password'], 401);
} elseif (!$request->device_name) {
return response()->json(['error' => 'Missing device_name'], 401);
} else {
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$auth = Auth::user();
$token = $auth->createToken($request->device_name)->plainTextToken;
$auth->setAttribute('token', $token);
return response()->json($token, 200);
}
return response()->json(['error' => 'The provided credentials do not match our records.'], 401);
}
}
Api.php
// Force all API responses to be in JSON format.
Route::middleware('json.response')->group(function () {
// UNAUTHENTICATED API's.
Route::post('login', [UserController::class, 'login']);
// AUTHENTICATED API's.
Route::middleware('auth:sanctum')->group(function () {
// USER.
Route::get('/user', function (Request $request) {
return $request->user();
});
// ADMIN.
Route::middleware('role.admin')->group(function () {
Route::get('/migrate', function () {
artisan::call('migrate');
return response()->json("success", 200);
});
Route::get('/route/clear', function () {
artisan::call('route:clear');
return response()->json("success", 200);
});
});
});
});
Frontend
const instance = axios.create({
withCredentials: true,
headers: {
Accept: 'application/json',
},
});
const loginWithEmailPasswordAsync = async (email, password) => {
// eslint-disable-next-line no-return-await
return await instance
.post('https://api.website.com/api/login', {
email,
password,
device_name: '_device',
})
.then((res) => {
return instance
.get('https://api.website.com/api/user')
.then((user) => {
return user;
})
.catch((error) => error);
})
.catch((error) => error);
};
try {
instance
.get('https://api.website.com/sanctum/csrf-cookie')
.catch((error) => error);
const loginUser = yield call(loginWithEmailPasswordAsync, email, password);....
Request and response headers when the 401 Unauthorized error pops up
Not sure if it matters but in my browser network tab I do see what seems to be the correct request headers such as the filled in X-XSRF-TOKEN, Cookie and in the response headers the set-cookie = laravel_session=ySiTVYYvyFKHSt9Q0VnP2vl4xeIee0MnjvlfQSu4; expires=Tue, 02-Aug-2022 18:55:13 GMT; Max-Age=7200; path=/; domain=.website.com; httponly; samesite=lax
I think you need to change SANCTUM_STATEFUL_DOMAINS from this
SANCTUM_STATEFUL_DOMAINS=localhost:8000,.website.com
to this:
SANCTUM_STATEFUL_DOMAINS=localhost:8000,customer.website.com
Since you'll have different .env for your dev and production environment, I think you should remove localhost host totally.
You can change your code from
SANCTUM_STATEFUL_DOMAINS=localhost:8000,.website.com
To
SANCTUM_STATEFUL_DOMAINS=website.com,frontend-subdomain-if-any.website.com
I am using Nuxt-Laravel-Sanctum.
While hosting on localhost for both api and client, login attempt is success and user response is getting back.
But, after uploading to server (api: api.repairtofix.com & client: admin-control.repairtofix.com) login seems to be success while user details is not getting back. I get error with 401 {message: "Unauthenticated."}
ie. It works on npm run dev, but after npm run generate it doesn't work
nuxt.config.js
axios: {
credentials: true
},
auth: {
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: 'https://api.repairtofix.com',
endpoints: {
login: {
url: '/api/login',
},
},
}
}
},
LoginController.php
public function __invoke(Request $request)
{
$credentials = $request->only('email', 'password');
if(!auth()->attempt($credentials)){
throw AuthenticationException();
}
}
api.php
use App\Http\Controllers\Auth\LoginController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Route::post('/login', LoginController::class);
cors.php
"Accept"=>"application/json",
'paths' => ['api/*', '/user', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
.env
SESSION_DOMAIN=.repairtofix.com
SANTUM_STATEFUL_DOMAINS=admin-control.repairtofix.com
Login.vue
data() {
return {
login: {
email: 'nmg.gta#gmail.com',
password: 'password'
}
}
},
methods: {
async userLogin() {
try {
let response = await this.$auth.loginWith('laravelSanctum', { data: this.login })
console.log(response)
} catch (err) {
console.log(err)
}
}
}
I found the solution
In sanctum.php file I forgot to add my domains. After adding I got the resopnse from /user
I am implementing JWT token for my laravel 6 project. I have followed this tutorial and successfully configured the application to use JWT. I am also able to login using auth()->attempt($credentials) which returns the JWT token.
Now when I try to access any other api route and send jwt token in request header for authorization, it always returns 401 (Unauthorized).
Here is my code
api.php
Route::group([
'prefix' => 'auth'
], function ($router) {
Route::post('login', 'Auth\React\RAuthController#login');
Route::post('get_user_pages', 'Auth\React\RAuthController#getUserPages');
});
axios request
const response = await axios.post('http://127.0.0.1:8000/api/auth/get_user_pages', {}, {
headers: {"Authorization": `Bearer ${cookie.load('token')}`}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
Controller function
public function getUserPages()
{
$auth_user_id = auth()->id();
$user_role_id = User::find($auth_user_id)->roles->makeHidden('pivot')->pluck('id')->first();
return $user_pages = ROLE::find($user_role_id)->pages->toArray();
}
I don't understand when I am able to access login route then why not other routes?
Try editing the route
Route::group([
'middleware' => 'api',
'prefix' => 'auth'
], function ($router) {
Route::post('login', 'Auth\React\RAuthController#login');
Route::post('get_user_pages', 'Auth\React\RAuthController#getUserPages');
});
Insideconfig/auth.php file:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
Till now I was storing access token in local storage. Now I want to move it into cookies.
So I added \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class, into Kernel.php. I also changed my axios call:
axios.get('/forum', {
params: {
filterBy: vm.filterBy,
filterDirection: vm.filterDirection,
theme: vm.theme
},
withCredentials: true,
headers: {
//'Authorization': 'Bearer ' + this.getToken,
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN':window.Laravel.csrfToken
}
}).then(function (response) {
vm.posts=response.data
}).catch(function (error) {});
You can see that I commented Authorization header where I was sending access token from local storage. I see that CSRF token is added in request header and that access token is saved in X-XSRF-TOKEN. There is also a Cookie with value: XSRF-TOKEN: (token value).
Now I am getting error with: Route[login] not defined.
Perhaps problem is that in Cookie value there is XSRF-TOKEN instead of laravel_token ?
Login api call
axios.post('/login', {
username: credentials.username,
password: credentials.password,
}).then(response => {
const token = response.data.access_token
localStorage.setItem('access_token', token)
localStorage.setItem('username', credentials.username)
resolve(response)
}).catch(error => {
reject(error)
})
And on back-end I then create token with:
$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' => $request->username,
'password' => $request->password,
]
]);
return json_decode((string) $response->getBody(), true);
I am creating JWT API authentication in Laravel with as stated in guide this link:
https://codeburst.io/api-authentication-in-laravel-vue-spa-using-jwt-auth-d8251b3632e0
Everything else is fine but I am having this issue on login:
error":"token_not_provided
Login seem to be going through successfully as I can see in the console or postman, but the token is never being retrieved. This is happening when the app tries to load the user data at api/auth/user end point.
GET http://127.0.0.1:8000/api/auth/user 400 (Bad Request)
this is my login method:
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if ( ! $token = JWTAuth::attempt($credentials)) {
return response([
'status' => 'error',
'error' => 'invalid.credentials',
'msg' => 'Invalid Credentials.'
], 400);
}
return response([
'status' => 'success'
]) ->header('Authorization', $token);
}
Then the user method
public function user(Request $request)
{
$user = User::find(Auth::User()->id);
return response([
'status' => 'success',
'data' => $user
]);
return $user;
}
Authentication Routes:
Route::post('auth/login', 'JWTAuthenticateController#login');
Route::group(['middleware' => 'jwt.auth'], function(){
Route::post('auth/logout', 'JWTAuthenticateController#APIlogout');
Route::get('auth/user', 'JWTAuthenticateController#user');
});
VueJS Login Component:
<script>
export default {
data(){
return {
email: null,
password: null,
error: false
}
},
methods: {
login(){
var app = this
this.$auth.login({
params: {
email: app.email,
password: app.password,
},
success: function () {
console.log('Success')
},
error: function () {
console.log('Something Went wrong!')
},
rememberMe: true,
redirect: {name:'inventories'},
fetchUser: true,
});
},
}
}
</script>
I cannot figure out how to fix this. Just don't tell why the authorization header not being returned:
return response([
'status' => 'success'
]) ->header('Authorization', $token);
Please Assist.