How can I create a Laravel Gate where client has to provide CSRF _token in headers when when using my api?
Idea is that I want somekind of gate when using my route:
http://127.0.0.1:50004/api/third-party/unsplash
Otherwise, anyone can copy and use above route.
api.php
Route::get('/third-party/unsplash', [UnsplashController::class, 'show'])
**// my gate here!!!!**
UnsplashController.php
public function show()
{
return ['authorizationKey' => 'Client-ID 1234'];
}
Unsplash.vue file:
const myAsync = async function fetchUnsplash() {
const myAPIKey = await fetch(
'http://127.0.0.1:50004/api/third-party/unsplash'
);
const dataMyAPIKey = await myAPIKey.json();
const response = await fetch('https://api.unsplash.com', {
headers: {
Authorization: dataMyAPIKey,
},
});
console.log(response);
};
Rather than relying on a CSRF token, you probably want full API authentication with something like Laravel Sanctum. Implementing Sanctum will help you protect your API effectively (including from CSRF attacks).
If you really do want to require a CSRF token in the header, you can implement that by adding custom middleware to those routes that would check for the CSRF token. You can look at the VerifyCsrfToken middleware as a starting point. Typically, the X-CSRF-TOKEN header is used to pass the CSRF token in a request.
Finally, you could potentially just use the built-in VerifyCsrfToken middleware if you choose to convert your /third-party/unsplash route from a GET to a POST.
All-in-all, I think using a more robust solution like Sanctum will serve you well and prevent other potential issues that you might not have thought of yet.
Related
I want to validate a token using Laravel Passport. My API's consumer will pass the token via the Authorization header as a Bearer token and I want that Laravel Passport returns me if is a valid token.
I don't want to use a middleware, my API will be in another Laravel Project, but I want this project to call the Laravel Passport server just for check if a token is valid, how can I check the token?
I'm issuing the tokens right, just left verify them, but I don't know how:(
This is how you can verify tokens without the middleware:
Auth::guard('api')->check();
You can create your own middleware. Inside that middleware's handle, pick the Bearer token and call your Passport server, depending on the response returned call next if true, or abort if false. Something like this:
public function handle($request, Closure $next)
{
try {
$passportEndpoint = 'your_passport_endpoint_here';
$client = Http::withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => $request->header('Authorization')
]);
$response = $client->get($passportEndpoint);
if ($response->status() === 200) {
$body = $response->object();
//do some stuff with response here, like setting the global logged in user
return $next($request);
}
}
catch (RequestException $exception) {
}
return abort(401, 'You are not authenticated to this service');
}
If you don't want to use the Passport middleware in the project where you want to validate the tokens, you would have to create an endpoint in the Laravel Passport server that can accept the token, perform the usual Passport validation and return a response to your service.
It would be an implementation of the Token Introspection spec: https://www.rfc-editor.org/rfc/rfc7662 - though you have to implement it yourself, as I think that Laravel Passport doesn't support it out-of-the-box.
Also, when verifying JSON Web Tokens (if this is the type of tokens that you use), remember that verifying the signature is not enough. Have a look at this best practices article to know how to properly work with JWTs.
I need your help with my Laravel API HTTP Request. I am trying to get a response from my api. But it sends back "Error: "Request failed with status code 401" and the AuthenthicationException shows "Unauthenticated." . I have not found any solutions. I need your help... Thanks.
const response = (await rootApi.get(`/openSesame/targets/${id}`)).data;
My 'Api\TargetController
public function detail($id)
{
$spintaxInput = SpintaxInput::find($id);
if (!$spintaxInput) {
return response()->json(['error' => "Not found."], 404);
}
return response()->json($spintaxInput, 200);
}
My api routes
Route::group(['middleware' => ['auth:web']], function () {
Route::group(['prefix' => 'openSesame'], function () {
Route::get('/targets/{id}', 'Api\TargetController#detail');
});
});
you should use Sanctum to API authenticate
Passport would be the way to go here.
check Passport
Now, to help you with this:
To be able to authenticate when you're going to make a request to the api, make sure to use the header: Authentication and the token followed by Bearer
A token example should be like this one:
Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiZThmNjZiMTU0Yjg4OGM2YmI2ODg0ZDM2ZDU3NGYxY2FiODFjODgyMmI0MDc5NDVkMTNjM2I2MDdiMDc0MGNkNTI1MzgxZDU2NWJkNWUwZTciLCJpYXQiOjE1ODY3MDY1MzgsIm5iZiI6MTU4NjcwNjUzOCwiZXhwIjoxNjE4MjQyNTM4LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.Brf48KrE3smifCIo61_8alvU8Yi5atsMtLz5t7-drQ7xnpG0Lga9q7Wh8RDJaYLtxHltdMLRVfp2HtstVQO6XY8qE0GNqS2pfjwFGDJTChWDSbINzjRyX9rO9FdTbE95TPlh84e_PRQ7iWMDO_DQkB67YvuYieXJjWAzF5UBGjK9ZMSjjzHahHV-iCE4Si_i0DHP6bLDTksZd51jiYV7ptGr41lZCwnL09fNjtWtLYTl79OoxIzcEPZMUQ_l7QcoJPJUYa0-lvHAta9hbkzZHdskIOB9C6afel4VxPFEVI0RmP5glJBJFKu0A_0N80iJf7yhXqofk5muF-bFWv9_092os2h3-zt0bDjTb7jeSAY8CkgxRQ3oLtRQN9MBgxRdUechycdimbKpU6hhpGfJfsofHtJiRGAbh5Eddlq5AGjdZkRW6zu9CjFUFiivZHOO_JI3HmU39jXUQx8218Czb9_Z-iG02K0Bvtk0eilvZl3k6FjvDka3beb0Zg99Da5MKeYSMKXqn4U-mndQPPtsidWCwh4foskzv6mRvWhsGh8xN0zByCTIhML-ogqrIGNcDUsrgpi2E4eue4PZ9DfYIa740kYKnJLzpNC6_ilQPesb3MjqLLx9DBcUkuWH7fwKKA_OaxhIv2WiELUECjWuIIDMpNGuK_Kes0RbqedPvlg
When using API's, you will need to use that header in each request. Otherwise you will get a 401. The 401 means that you're not logged in.
You should use passport, like said.
And for auth middleware, replace it to:
middleware('auth:api')
This question already has answers here:
Access-Control-Allow-Origin with instagram api
(1 answer)
CORS error, when i use instagram API with angularjs
(1 answer)
Closed 3 years ago.
I'm trying to fetch some images from my Instagram account in a Laravel application with Vue as front end. When I try to do it in a standalone Vue app, it works well, but when I do so with Laravel, I got a message saying "has been blocked by CORS policy: Request header field x-csrf-token is not allowed by Access-Control-Allow-Headers in preflight response."
I'm using Laravel 5.8 and the Vue and Axios that comes within in and I'm using Homestead as my localhost server.
I've tried a lot of tips that I found here and on Google but I had no success. Basically, I'm trying the very basic of Axios call
beforeMount() {
axios.get('https://api.instagram.com/v1/users/self/media/recent/?access_token=[MY_ACCESS_TOKEN]').then(response => console.log(response))
}
I already created a Cors middleware on Laravel and tried a lot of headers settings on Axios.
I'm basically trying to retrieve a list of my Instagram posts and bypass that cors / x-csrf error.
Laravel automatically applies the X-CSRF-TOKEN header to all axios requests. This is so you can communicate with your application without having to pass the CSRF token every time for POST, PUT, DELETE, etc.
resources/js/bootstrap.js (default settings)
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
You should be able to remove the offending header by doing something like this:
beforeMount() {
// create a new instance so we don't delete the csrf token for other requests
let instance = axios.create();
// delete the x-csrf-token header
delete instance.defaults.headers.common['X-CSRF-TOKEN'];
// use the new instance to make your get request
instance.get('https://api.instagram.com/v1/users/self/media/recent/?access_token=[MY_ACCESS_TOKEN]')
.then(response => console.log(response))
}
Your AJAX request to the Instagram API endpoint has to be sent as a jsonp request which means the dataType of the request has to be jsonp.
This blob in axios repository contains an example of sending a request using jsonp which is mentioned below.
Install jsonp package, if you haven't already.
npm install jsonp --save
and then;
const jsonp = require('jsonp');
jsonp('http://www.example.com/foo', null, (err, data) => {
if (err) {
console.error(err.message);
} else {
console.log(data);
}
});
Below is an example of sending a request using jQuery method with jsonp dataType to the Instagram API endpoint.
$.ajax({
url: "https://api.instagram.com/v1/users/self/media/recent/?access_token=[MY_ACCESS_TOKEN]",
type: "GET",
crossDomain: true,
dataType: "jsonp",
success: function(response){
console.log(response);
}
});
I'm in Laravel 5.6. I have all my API routes built out and properly responding to requests from my REST client (Paw). I'm trying to build a simple front end to access those routes.
I'm trying to use Laravel's out-of-the-box features as much as possible, so I'm using Axios to call those routes from a blade template using Vue.js. It works if I disable auth middleware on the test route, but I get 401 errors on the console when auth middleware is enabled for the route.
The problem seems obvious enough... The auth:api guard on my /api routes wants to see an oauth token in the header, but when I log in with the web page it does session authentication. I assume there's a simple way to resolve this without having to spoof an oauth token request in the web frontend, right? Do I need to somehow pass the session token in my request with Axios? And, if so, do I also need to change the auth:api guard in my api routes file?
I solved it! I'm a bit embarrassed because the answer was actually in the Laravel docs, but I will say I tried this before posting the question here and it wasn't working. Perhaps something else was broken at the time.
Per the Laravel docs:
All you need to do is add the CreateFreshApiToken middleware to your
web middleware group in your app/Http/Kernel.php file:
'web' => [
// Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
This Passport middleware will attach a laravel_token cookie to your
outgoing responses. This cookie contains an encrypted JWT that
Passport will use to authenticate API requests from your JavaScript
application. Now, you may make requests to your application's API
without explicitly passing an access token...
You will probably want to use Larvel Passport or a JWT auth mechanism for obtain the Authorization token.
Seeing as how you're using axios, add a request interceptor to attach the access token to every request once you successfully authenticate. A simple example:
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// assume your access token is stored in local storage
// (it should really be somewhere more secure but I digress for simplicity)
let token = localStorage.getItem('access_token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
to use the auth:api first you need api_token inside your users table
Schema::table('users', function ($table) {
$table->string('api_token', 80)->after('password')
->unique()
->nullable()
->default(null);
});
also you can create a user for testing as follows
User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'api_token' => Str::random(60),
]);
in your layout use the following before #yield('content')
<script>
window.Laravel = <?php echo json_encode(['api_token' => (Auth::user())->api_token]); ?>
</script>
now you can use window.laravel.api_token inside your vue js to use it in headers
heres an example
var methods = new Vue({
el: '#tabs_lists',
data: {
config: {
headers: {
Authorization: 'Bearer ' + window.Laravel.api_token,
Accept: 'application/json'
}
},
data: []
},
methods: {
test: function (link) {
axios.get(link, this.config)
.then(response => (this.data = response.data)).catch(function (error) {
// handle error
console.log(error);
});
}
}
}
)
I'm not sure if I'm using the correct method for my problem.
I would like to get Infos via Axios. For example: calling: /api/user with Axios gets me the user, but I don't want to see the information when I go on domain.test/api/user.
I even want to use API calls to get Results of Functions even if the User is a Guest.
I installed everything there is on the Laravel Documentation for API, but still I'm not sure how the user gets the Token.
So if I call:
axios.get('/api/user')
.then(response => {
console.log(response.data);
});
I get from the network tab {"message":"Unauthenticated."}. (I didn't forget to set: ...Middleware\CreateFreshApiToken::class and everything there is).
I think my problem is that I didn't register the User correctly. I got my two keys what should I do with it?
And then it's weird, reading the blog https://laravelcode.com/post/laravel-passport-create-rest-api-with-authentication, they use
$success['token'] = $user->createToken('MyApp')->accessToken;
But I don't get it. Where do I save it? I'm super confused, because every blog about shows Laravel Passport completely differently.
Or am I doing it wrong?
Passport is an OAuth2 server implementation which offers several different authorization strategies. While it is certainly possible to use Passport for authentication, it's primary purpose is authorization. This is done via token scopes. The type of token you choose to issue depends on your intended purpose with the API and whom your intended target is to be consuming your api.
Let's start with the following:
$success['token'] = $user->createToken('MyApp')->accessToken;
Here they are creating Personal Access Tokens. This is the simplest means of consuming your api, since clients are able to create the token themselves as needed and are long lived.
The Password Grant strategy is also a good option. A common approach when using this strategy is proxying authentications to Passport internally and merging the client id and secret into the request in a middleware.
public function handle($request, $next) {
return $next(tap($request, function($request) {
$request->merge([
'client_id' => config('services.passport.client.id'),
'client_secret' => config('services.passport.client.secret')
]);
// do any other pre-request work needed here
}));
}
Setup
To get started with Passport, run the command:
php artisan passport:install
This will create the encryption keys as well as a personal access and password grant client types, which will be stored in your database in the oauth_clients table.
Guard and Middleware
API routes need to be using the api guard, done by setting auth:api middleware on the route group(s) or Controller constructor.
// using via constructor
public function __construct()
{
$this->middleware('auth:api');
}
// via a route group
Route::group(['middleware' => ['auth:api'], function() {
// your api routes
});
Passport Routes
Your user model needs to be using the trait HasApiTokens and within the AuthServiceProvider call Passport::routes() in the boot method.
public function boot()
{
$this->registerPolicies();
Passport::routes();
// this is also an ideal time to set some token expiration values
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
}
To use the personal access token strategy for authentication, you need to create your own login and logout routes instead of using the builtin Laravel auth mechanisms.
Referring to the tutorial in your question, they have defined the following routes:
Route::post('login', 'API\PassportController#login');
Route::post('register', 'API\PassportController#register');
and the login method implemented as:
public function login(){
if(Auth::attempt(['email' => request('email'), 'password' => request('password')])){
$user = Auth::user();
$success['token'] = $user->createToken('MyApp')->accessToken;
return response()->json(['success' => $success], $this->successStatus);
}
else{
return response()->json(['error'=>'Unauthorised'], 401);
}
}
This is expecting a ajax request with an email and password to be posted to login and if successful it responds with an access token.
Storing/Using the Token
This token must be stored client side, either in a cookie or local storage, for example, and then added to every request from the client there after. You'll add the token to the Authorization header in client requests.
// Use a response interceptor to check for a token and put it in storage
axios.interceptors.response.use(response => {
// store the token client side (cookie, localStorage, etc)
if (response.data.token) {
localStorage.setItem('token', token)
}
return response
}, error => {
// Do something with response error
return Promise.reject(error);
});
// Use a request interceptor to add the Authorization header
axios.interceptors.request.use(config => {
// Get the token from storage (cookie, localStorage, etc.)
token = localStorage.getItem('token')
config.headers.common['Authorization'] = `Bearer ${token}`
return config;
}, error => {
// Do something with request error
return Promise.reject(error);
});
Accessing the Authenticated User
In order to get the user, pass the 'api' parameter to the auth() helper, Auth facade, or through the request:
$user = auth('api')->user();
$user = Auth::user('api');
$user = request()->user('api');
Keep in mind personal access tokens are long lived, so it's up to you to decide when and how they should expire.
Any http client can be used to make api requests, axios is a very common option and included with each Laravel installation.