How to Secure Laravel API (sanctum) - javascript

I have a laravel application with a login system, only for interns
and
I have a Website which gets and posts data from/to the laravel application with a api
So i want, that only my website can get/post data from/to the api -> laravel app
For now, i've created a login user for the api with email and password. I know thats not the right way to do it.
And with this login credentials the website gets a bearer token from the api (expiration 10 min)
With this bearer token (header of ajax call) the website calls every api request (get/post)
Code:
Ajax call to get Bearer Token:
axios.post(data_source_url + "/auth/token", {'email' : 'api#email.de', 'password' : 'pw1'}).then((res) => {
document.cookie = 'bearer=' + res.data + ';expires=' ...
})
Laravel Api Routes:
Route::post('/auth/token', ['uses'=>'ApiController#getToken'])->name('api.getToken');
Route::get('/get', ['middleware'=>'auth:sanctum', 'uses'=>'Api\ApiController#read']);
Route::post('/send', ['middleware'=>'auth:sanctum', 'uses'=>'Api\ApiController#send']);
so I would like to leave it that way with authorise with a bearer token, but how to send and receive a bearer token from the api the right/secure way, because the way i do it right now with the login is totally insecure

You should not leave open your route to get the token. I've written an article in this matter Laravel 8 REST API
This approach lets you get a token by first logging in with a valid user to the app. For your use case, you just remove the register route and controller, and you will be able to secure it just for your use.
Route
Route::post('/login', [ApiController::class, 'login']);
Controller
public function login(Request $request) {
if (!Auth::attempt($request->only('email', 'password'))) {
return response()->json(['message' => 'Incorrect e-mail or password'], 401);
}
$user = User::where('email', $request['email'])->firstOrFail();
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
I just realized you are logging in to retrieve the bearer. Relax, headers are encrypted using HTTPS, so it is secure as far as I know.
In order to remove the plaintext user and password in the js call, you need to install dotenv by issuing npm install dotenv --save
Now add to your .env file
MY_API_USER=api#email.de
MY_API_PASSWORD=pw1
Then you need to add to your file
require('dotenv').config();
So you can use the sensitive data this way
axios.post(data_source_url + "/auth/token", {'email' : process.env.MY_API_USER, 'password' : process.env.MY_API_USER}).then((res) => {
document.cookie = 'bearer=' + res.data + ';expires=' ...
})
You won't share your .env file, and be sure to add it to your .gitignore or similar, if it is not there.

Related

Laravel + Angular - Get 401 unauthenticated on 1 GET method

I'm developing a Laravel + Angular app and i'm getting 401 Unauthorized in only 1 GET request.
Here I explain how I developed my authentication and how it work on Backend and Frontend. I wish you can help me.
I use Laravel Sanctum for manage authentication in my app. Here is how I program the backend.
I get users from my BD table:
Note: I have created a separate controller, to separate the authentication functions from the user functions, even so, I have tried to put this function in my AuthController and it has not given me any result.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
class UsersController extends Controller
{
public function getAllUsers()
{
return User::all();
}
}
As I want you to only be able to retrieve all the DB users if you are authenticated, in my api.php file I put the path inside the middleware:
Route::middleware('auth:sanctum')->group(function()
{
Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout']);
Route::get('getAuthUser', [\App\Http\Controllers\AuthController::class, 'getAuthUser']);
//Admin actions
Route::post('createUser', [\App\Http\Controllers\AuthController::class, 'createUser']);
Route::get('getAllUsers', [\App\Http\Controllers\UsersController::class, 'getAllUsers']);
});
If I make the request from the Postman everything works correctly, if I am not authenticated it gives me an error and if I have previously authenticated it returns all the DB users just as I expected. By the way, I am using cookies to send the jwt to the Frontend.
The problem is when in my Angular app I request my backend with the GET method to retrieve these users and display them in a table. In addition, the code to retrieve the users is within a condition in which it is looking at whether the user is authenticated or not. The truth is that I do not understand what may be happening.
getUsers(): void
{
//Check if user is authenticated
this.http.get('http://localhost:8000/api/getAuthUser', { withCredentials: true }). subscribe(
(res: any) =>
{
Emitters.authEmitter.emit(true);
Emitters.roleEmitter.emit(res.role);
//Get all users
this.http.get('http://127.0.0.1:8000/api/getAllUsers', { withCredentials: true }). subscribe(
res =>
{
this.users = res;
}
)
},
err =>
{
Emitters.authEmitter.emit(false);
Emitters.roleEmitter.emit("none");
alert("You should be authenticated for this.");
}
);
}
The first request that you see above getAuthUser, makes the request to the Backend in the same way as the second request getAllUsers and the first one works perfectly and the second one does not, it is in which I get an err. I call the getUsers() method in the ngInit().
I hope I have explained myself well. Any information you need to know let me know. Thank you.
The solution was in the request that gave the error to change the path of the api, instead of putting 127.0.0.1 putting localhost.

How can access google calendar of user and edit it without asking for user permisssion again and again

On my website, I am asking for google calendar access. I can edit the user calendar but, I don't want to ask for user permission, again and again, so once the user authorized and give access to google calendar, I can edit it anytime until the user revokes the access. Should I implement it on the frontend or the backend and how? I checked few answers where they mention we can use a service account but, it is not clear how can I edit or read the individual user's calendar events and how can I remove it once the user revokes access. This question was deleted because code was missing so adding code below.
I tried this so once user login I get access token and I am using it
window.gapi.load("client:auth2", () => {
window.gapi.client.setApiKey("api_key");
window.gapi.client.load("https://content.googleapis.com/discovery/v1/apis/calendar/v3/rest")
.then(() => {
window.gapi.auth.setToken({ access_token: access_token })
window.gapi.client.calendar.events.insert({
"calendarId": "id",
'resource': event
}).then((res) => {
console.log("calendar data res "+JSON.stringify(res))
}).catch(err => console.log("error getting calendar data "+JSON.stringify(err)))
}).catch(err => console.error("Error loading GAPI client for API", err) )
})
but once access token expires how can I get a new access token( I don't want to show login popup to the user again and again. I want to know how can I do it using refresh token on client-side).
You can't get a refresh token on the client-side without exposing your secret key to the public.
You can create an endpoint that accepts oAuth code and return the token, save the refresh token for later. You set up a corn job that checks for expired token and refreshes them.
Every time the user accesses your app, you grab a fresh token from the server and proceed to work normally.
As per Google guidelines. You do POST to https://oauth2.googleapis.com/token. Assuming your server-side stack is in Node.js, you do something like this using an HTTP client like Axios:
const Axios = require('axios');
const Qs = require('querystring');
const GOOGLE_CLIENT_ID = 'abc';
const GOOGLE_CLIENT_SECRET = '123';
let refreshToken = getFromDataBase(); // should be stored in database
Axios.post('https://oauth2.googleapis.com/token', Qs.stringify({
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
refresh_token: refreshToken,
grant_type: 'refresh_token'
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
})
.then(({ data }) => console.log(data.access_token)) // new token that expires in ~1 hour
.catch(console.log)
Firstly, do you (a) want to update the calendar when the user is not logged in, for example in response to an external event? ... OR ... do you (b) only want to update the calendar from within a browser session?
If (a), then you need to ask the user for offline access which will give you a Refresh Token , which you can securely store on a server and use whenever you need to. (Forget all about Service Accounts).
If (b), then you need the following pieces of information :-
When the access token expires, request access again, but add the flag prompt=none. This will give you a fresh Access Token without the user seeing any UX.
Do this in a hidden iframe so that it is happening in the background and is invisible to the user. Your iframe will therefore always have an up to date Access Token which it can share with your app via localStorage or postMessage.

Express redirect user and send data along with it to be accessed by angular 8 client

The scenario is payment gateway post some payment data to url '/payment-check'. On successful verification, I redirect the user to a particular url and set the header to be accessed by the angular client later.
Now the problem is headers are set in the post method and from angular, I can't make post method because payment gateway is posting data.
I also don't have payment data in the get method(express server) to send to the angular client.
Now how can I get set headers(payment data) in angular?
Or is there any alternative / easier way to achieve it.
One of the ways to do is querystring. But as payment_data is sensitive it is not a good idea to expose it in url
Here is my code
express.js file
router.post('/payment-check', function(req,res ) {
var options = req.body
console.log(options)
let secret ='XXXXXXXXX'
console.log(options.razorpay_order_id + "|" + options.razorpay_payment_id, secret)
if(// verification code) {
console.log("matched")
var payment_id = options.razorpay_payment_id
res.setHeader('payment_id' ,payment_id)
res.redirect('http://localhost:8100/menu/items/carts/payment-options/netbanking/order-success')
}
})
angular service
getPayemnt_id(): Observable<any> {
return this.http.get<any>(this.url + 'payment-check')
}
.ts page
this.serivename.getPayemnt_id().subscribe(data =>{
console.log(data)
})

How to get OAuth token from ebay API using express, node, javascript

What combination of requests and responses are needed to get an Oauth token from eBay? What is a runame and what headers do I need to keep eBay happy?
After three frustrating days of trying to get Ebay's oauth to give me an access token, I have finally worked it out. As the docs are pain and there is little to no help online, I have decided to post my solution here in the hope that it will help others. I am no good at StackOverflow so let me know if I need to improve my formatting.
app.get("/login/ebay", (req, res) => {
res.redirect(`https://auth.sandbox.ebay.com/oauth2/authorize?client_id=DeanSchm-TestApp-SBX-b843acc90-fd663cbb&redirect_uri=Dean_Schmid-DeanSchm-TestAp-kqmgc&response_type=code`
);
});
The first thing you need to do is redirect to this URL.
The format is like this
https://auth.sandbox.ebay.com/oauth2/authorize?client_id=&redirect_uri=&response_type=code
There is also a scope property, but I don't understand that yet, and I got back a token without is so me.
That URL takes you to the eBay login page. If you are using the sandbox, you need to create a sandbox user and login with sandbox credentials.
Once you log in, eBay will redirect you to a URL of your choosing. You enter the URL you want to be redirected to here.
It's in the ebay developer section under Get A Token From Ebay Via your Application.
This URL can be anything. you just have to handle it in node or express or whatever, because as soon as someone signs in that URL is where they are heading.
Here is how I handled it
app.get("/auth/ebay/callback", (req, res) => {
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization:
"Basic " +
btoa(
`client public key:client secret keys`
)
},
data: qs.stringify({
grant_type: "authorization_code",
// parsed from redirect URI after returning from eBay,
code: req.query.code,
// this is set in your dev account, also called RuName
redirect_uri: "Dean_Schmid-DeanSchm-TestAp-kqmgc"
})
})
.then(response => console.log(response))
.catch(err => console.log(err));
});
A few gotchas that got me.
Make sure you have space after "Basic " in the authorisation
header.
bota is a 3rd party library that base 64 encodes your public and
secret keys. There are many ways to do this. I just did it this way because I stole a bunch of code.
With Axios, the request body is called data but with fetch and other
methods it might be called something else like body or param
The Axios method is in a get request because of the redirect from ebay
defaults to an http get.
ebay now uses https. Make sure you are using
sandbox URLs
We also had to use JS for the eBay API and solved your mention problem with developing a new Lib. It's available here. This lib will also automatically try to refresh the token if it's expires.
This is how we obtain the oAuth token:
import eBayApi from 'ebay-api';
const eBay = new eBayApi({
appId: '-- or Client ID --',
certId: '-- or Client Secret',
sandbox: false,
siteId: eBayApi.SiteId.EBAY_US,
ruName: '-- eBay Redirect URL name --' //in this case: Dean_Schmid-DeanSchm-TestAp-kqmgc
});
// This will generate the URL you need to visit
const url = eBay.oAuth2.generateAuthUrl();
// After grant access, eBay will redirect you to RuName page and set the ?code query.
// Grab the ?code and get the token with:
eBay.oAuth2.getToken(code).then((token) => {
console.log('Token', token);
ebay.oAuth2.setCredentials(token);
// Now you can make request to eBay API:
eBay.buy.browse.getItem('v1|382282567190|651094235351')
.then(item => {
console.log(JSON.stringify(item, null, 2));
})
.catch(e => {
console.log(e);
});
});
Another example with scope can we found here.
Some hints:
with "scope" you tell eBay what you plan to use. You can find the
Descriptions here, under Sandbox/Production Keys Box. (OAuth
Scopes)
if you use axios you can use the auth config, so you dont't
need btoa:
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
// ...
auth: {
username: 'appId',
password: 'certId'
}
});
To use sandbox without https, e.g. localhost, you can setup a redirect on a https site and redirec/pass the code to non-https site.

EmberJS Rails API security

Setup is an Ember frontend with a rails backend using JSON api.
Everything is going fine but some questions do come up:
How do I ensure only the emberjs application consumes the api? I wouldn't want a scripter to write an application to consume the backend api.
It all seems pretty insecure because the EmberJS application would come in a .js file to the client.
How would I ensure a user is really that user if everyone has access to a JS console?
You can extend the RESTAdapter and override the ajax method to include your authentication token in the hash, and you need make sure your controllers validate that token.
In my environment (.NET), I have the authentication token in a hidden field of the document which my app renders, so my ajax override looks like this:
App.Adapter = DS.RESTAdapter.extend({
ajax: function(url, type, hash, dataType) {
hash.url = url;
hash.type = type;
hash.dataType = dataType || 'json';
hash.contentType = 'application/json; charset=utf-8';
hash.context = this;
if (hash.data && type !== 'GET') {
hash.data = JSON.stringify(hash.data);
}
var antiForgeryToken = $('#antiForgeryTokenHidden').val();
if (antiForgeryToken) {
hash = {
'RequestVerificationToken': antiForgeryToken
};
}
jQuery.ajax(hash);
}
});
The token can come from a cookie or whatever you define, as long as you're able to include it in the request header and have your controllers validate it (possibly in before_filter), it should enough.
Then in the Store, pass the new adapter instead of the default (which is RESTAdapter)
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter.create()
})
Note: RESTAdapter#ajax will be changed in favor or Ember.RSVP, making this override deprecated. It must be updated after the next release, but should be ok for revision 12.
I am using Ember Simple Auth to great effect for user authentication and API authorisation.
I use the Oauth 2 user password grant type for authentication of the user and authorising the application by way of a bearer token which must be sent on all future API requests. This means the user enters their username/email and password into the client app which then sends to the server via HTTPS to get an authorisation token and possibly a refresh token. All requests must be over HTTPS to protect disclosure of the bearer token.
I have this in app/initializers/auth:
Em.Application.initializer
name: 'authentication'
initialize: (container, application) ->
Em.SimpleAuth.Authenticators.OAuth2.reopen
serverTokenEndpoint: 'yourserver.com/api/tokens'
Em.SimpleAuth.setup container, application,
authorizerFactory: 'authorizer:oauth2-bearer'
crossOriginWhitelist: ['yourserver.com']
In app/controllers/login.coffee:
App.LoginController = Em.Controller.extend Em.SimpleAuth.LoginControllerMixin,
authenticatorFactory: 'ember-simple-auth-authenticator:oauth2-password-grant'
In app/routes/router.coffee:
App.Router.map ->
#route 'login'
# other routes as required...
In app/routes/application.coffee:
App.ApplicationRoute = App.Route.extend Em.SimpleAuth.ApplicationRouteMixin
In app/routes/protected.coffee:
App.ProtectedRoute = Ember.Route.extend Em.SimpleAuth.AuthenticatedRouteMixin
In templates/login.hbs (I am using Ember EasyForm):
{{#form-for controller}}
{{input identification
label="User"
placeholder="you#example.com"
hint='Enter your email address.'}}
{{input password
as="password"
hint="Enter your password."
value=password}}
<button type="submit" {{action 'authenticate' target=controller}}>Login</button>
{{/form-for}}
To protect a route I just extend from App.ProtectedRoute or use the protected route mixin.
Your server will need to handle the Oauth 2 request and response at the configured server token endpoint above. This is very easy to do, Section 4.3 of RFC 6749 describes the request and response if your server side framework doesn't have built-in support for Oauth2. You will need to store, track and expire these tokens on your server however. There are approaches to avoiding storage of tokens but that's beyond the scope of the question :)
I have answered the backend question and provided example rails example code for user authentication, API authorisation and token authentication here

Categories

Resources