In PHP Application, I've created following middleware to validate API Key:
$apiKeyValueFromHeader = $request->header('Authorization');
$apiKeyValueFromQuery = $request->get('api_key');
if (empty($apiKeyValueFromHeader) && empty($apiKeyValueFromQuery)) {
throw new ApiKeyNotFoundException("API Key Not Found");
}
//Get API_KEY from header
$apiKeyFromHeader = null;
if ( ! empty($apiKeyValueFromHeader)) {
$bearer = explode(' ', $apiKeyValueFromHeader);
$apiKeyValue = $bearer[1];
$apiKeyFromHeader = $this->isApiKeyVerifiedFromHeader($apiKeyValue);
}
//Get API_KEY from QueryString
if (empty($apiKeyFromHeader)) {
$apiKeyFromQuery = $this->isApiKeyVerifiedFromQuery($apiKeyValueFromQuery);
if (empty($apiKeyFromQuery)) {
throw new InvalidApiKeyException("Unauthorized Access!");
}
$apiKey = $apiKeyFromQuery;
} else {
$apiKey = $apiKeyFromHeader;
}
$apiKey->update([
'last_used_at' => Carbon::now(),
'last_ip_address' => $request->ip(),
]);
$apikeyable = $apiKey->apikeyable;
$request->setUserResolver(function () use ($apikeyable) {
return $apikeyable;
});
$request->apiKey = $apiKey;
event(new ApiKeyAuthenticated($request, $apiKey));
return $next($request);
But I couldn't find solution to identify from which URL(source) the API request is coming from. The API Key could be used by developers or any 3rd party integrating services like Zapier.
Can anyone help me to identify source of request coming from so that I could restrict the access?
In backend, I could define the URL for provided API Key but I do not know how could I prevent unauthorized access.
I do not want to use OAuth2 i.e. client/secret
yes you can surely do that using passport.
Please see passport documentation here
You can create particular users for particular third-party/sources. And a unique token will be assign to each user. Through which you can restrict unauthorized access as well as you can identify which third party is accessing API's
Related
in one of my projects i want to add a google one tap login for that i followed instructions as mentioned.
The front end is working fine but there is issue with the backend.
Here is my code.
I have added this script to the header.
and this code after body open
<div id="g_id_onload"
data-client_id="#####################.googleusercontent.com"
data-login_uri="/login/google/oneTap"
data-_token="{{csrf_token()}}"
data-method="post"
data-ux_mode="redirect"
data-auto_prompt="true">
</div>
This is the route
Route::get('/login/google/oneTap', [App\Http\Controllers\SocialLoginController::class, 'oneTap']);
In an article regarding one, tap login author told that it requires a post method but there is clarification on how to add a post method.
This is the article.
https://www.teachnep.com/blog/how-to-add-one-tap-login-to-laravel-project#
My backend code.
public function oneTap(REQUEST $request)
{
$token = $request->credential;
$tokenParts = explode('.', $token);
$tokenHeader = base64_decode($tokenParts[0]);
$tokenPayload = base64_decode($tokenParts[1]);
$jwtHeader = json_decode($tokenHeader);
$jwtPayload = json_decode($tokenPayload);
$user = $jwtPayload;
return $user;
}
It returns null;
Any help would be appreciated.
You must define your route using the POST verb instead of GET.
For example:
Route::post('/login/google', [GoogleSignInController::class, 'login'])
->name('login.google');
Even though you can verify the "ID Token" (this is how Google refers to the credential param) by yourself, it is recommended to use the official Google API Client Library.
You can add it to your project using Composer:
composer require google/apiclient
In addition to validate the credential field, you should validate the CSRF token provided in the g_csrf_token field.
To summarize:
/**
* Validate the "ID Token" using the Google API Client Library.
* https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
*/
public function login(Request $request)
{
if ($_COOKIE['g_csrf_token'] !== $request->input('g_csrf_token')) {
// Invalid CSRF token
return back();
}
$idToken = $request->input('credential');
$client = new Google_Client([
'client_id' => env('GOOGLE_CLIENT_ID')
]);
$payload = $client->verifyIdToken($idToken);
if (!$payload) {
// Invalid ID token
return back();
}
dd($payload);
}
Note that I printed the $payload at the end:
You may want to use the $payload['sub'] that represents the user id to register / authenticate this user in your application, as well as its name and picture.
So, I have integrated keycloak API endpoints in my Symfony project..
Regarding https://ultimatesecurity.pro/post/password-policy/ we have added 'not username' policy to test on creating new user within the app.
Idea is to delcare specific method with defined endpoint which will do this.
I was checking the documentation and could not find any endpoint that can check for password policy rules --> documentation
Idea for it:
$options = [
'headers' => $this->getAuthJsonHeaders()
];
try {
$endpoint = sprintf('auth/admin/realms/%s/', $this->realm);
$response = $this->request('GET', $endpoint, $options);
return $response;
} catch (\Exception $e) {
$this->exception('Can`t reset user password on Keycloak. ' . $e->getMessage());
}
This is what I get:
when dumping results
To get the list of the password policies being used by the Realm, you should call the following endpoint:
GET <KEYCLOAK_HOST>/auth/admin/realms/<YOUR_REALM>
from the JSON response extract the field:
passwordPolicy
which for instance if you have set Minimum length to 12 and Hashing Iterations to 27500 the passwordPolicy would be "length(12) and hashIterations(27500)"
I am trying to find the logged in user in my application using Auth but i get trying to get property of non-object which i understand clearly that it is returning null.
In my code below, an event triggers my webhook and post is sent to the address below. The function orderCreateWebhook triggers but that is where the error comes from..
The line $get_template = Order::where('id', Auth::user()->id);. Why is Auth returning null please? I am logged as well because i use auth in this same controller for another function which works fine.
Is it because it a webhook ?
Controller
public function registerOrderCreateWebhook(Request $request)
{
$shop = "feas.myshopify.com";
$token = "8f43d89a64e922d7d343c1173f6d";
$shopify = Shopify::setShopUrl($shop)->setAccessToken($token);
Shopify::setShopUrl($shop)->setAccessToken($token)->post("admin/webhooks.json", ['webhook' =>
['topic' => 'orders/create',
'address' => 'https://larashop.domain.com/order-create-webhook',
'format' => 'json'
]
]);
}
public function orderCreateWebhook(Request $request)
{
$get_template = Order::where('id', Auth::user()->id);
$baseurl = "https://apps.domain.net/smsapi";
$query = "?key=7e3e4d4a6cfebc08eadc&to=number&msg=message&sender_id=Shopify";
$final_uri = $baseurl.$query;
$response = file_get_contents($final_uri);
header ("Content-Type:text/xml");
}
In your function registerOrderCreateWebhook you appear to be making a request to shopify api and providing your webhook as the address which shopify will redirect the user to upon success. If this is correct, that request does not know about the user who generated the original request that made the api request since the request is coming from a completely different origin.
You would need to pass some key along with the url and then obtain the user within orderCreateWebhook. Something like:
Shopify::setShopUrl($shop)->setAccessToken($token)->post("admin/webhooks.json",
['webhook' =>
['topic' => 'orders/create',
'address' => 'https://larashop.domain.com/order-create-webhook/some-unique-key',
'format' => 'json'
]
]);
My suggestion would be to have a unique hash stored somewhere that relates back to the user in your system, perhaps a column in your users table. I wouldn't use the user_id for security reasons. So you would end up with something like:
//route
Route::get('/order-create-webhook/{uniqueKey}', 'YourController#orderCreateWebhook');
//or
Route::post('/order-create-webhook/{uniqueKey}', 'YourController#orderCreateWebhook');
// depending on the request type used by api which calls this endpoint
// controller function
public function orderCreateWebhook($uniqueKey, Request $request)
{
$user = User::where('unique_key', $uniqueKey)->first();
$get_template = Order::where('id', Auth::user()->id);
$baseurl = "https://apps.domain.net/smsapi";
$query = "?key=7e3e4d4a6cfebc08eadc&to=number&msg=message&sender_id=Shopify";
$final_uri = $baseurl.$query;
$response = file_get_contents($final_uri);
header ("Content-Type:text/xml");
}
Is it because it a webhook ?
Yes, you can't use sessions in a webhook. It's the shopify server which is making the call. You should read the doc, it may exist a way to give an unique identifier in your call to shopify api and get it back in the webhook to find your user associated.
just use this to get authenticated user
use the facade in your class/Controller
use Illuminate\Support\Facades\Auth
public function getAuthUser(){
$user = Auth::user()
if(!is_null($user)
{
//user is authenticated
}
else
{
// no user
}
}
EDIT:
Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83
MY ORIGINAL QUESTION:
I'm implement with jwt-auth my protected resources that require an authenticated user with bellow code:
Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function() {
// Protected routes
});
When user 'sign in' on API an Authorization token is created, and sent on response Authorization header to client application that call the resource. So, client applications when intercept a Authorization token on header of any response, set a variable/session/whatever with this token value, to send again to API on next request.
The first request for a protected resource after 'login' works fine, but the next client application request to API with a refreshed token, gives the following error (API mount all responses in json format):
{
"error": "token_invalid"
}
What can be happen with refreshed tokens? My refresh token implementation (set as a after middleware) is wrong? Or isn't necessary to manually refresh all Authorization token that come with client apps requests?
UPDATE:
I update the jwt-auth RefreshToken middleware as propose here, but the token_invalid persist.
BUG:
I guess that I found what happens. Note that in the refresh method, old token is added to blacklist cache case enabled:
// Tymon\JWTAuth\JWTManager
public function refresh(Token $token)
{
$payload = $this->decode($token);
if ($this->blacklistEnabled) {
// invalidate old token
$this->blacklist->add($payload);
}
// return the new token
return $this->encode(
$this->payloadFactory->setRefreshFlow()->make([
'sub' => $payload['sub'],
'iat' => $payload['iat']
])
);
}
And note that in add to blacklist method the key is the jti param from old token payload:
// Tymon\JWTAuth\Blacklist
public function add(Payload $payload)
{
$exp = Utils::timestamp($payload['exp']);
// there is no need to add the token to the blacklist
// if the token has already expired
if ($exp->isPast()) {
return false;
}
// add a minute to abate potential overlap
$minutes = $exp->diffInMinutes(Utils::now()->subMinute());
$this->storage->add($payload['jti'], [], $minutes);
return true;
}
Thus, when has on blacklist method is called, the old token jti param is the same that the new, so the new token is in blacklist:
// Tymon\JWTAuth\Blacklist
public function has(Payload $payload)
{
return $this->storage->has($payload['jti']);
}
If you don't need the blacklist functionality just set to false on jwt.php configuration file. But I can't say if it expose to some security vulnerability.
Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83
When I get this issue, the solution that I found to get my project working was to generate a new token with data from older token on each new request.
My solution, that works for me, is bad, ugly, and can generate more issues if you have many async requests and your API(or business core) server is slow.
For now is working, but I will investigate more this issue, cause after 0.5.3 version the issue continues.
E.g:
Request 1 (GET /login):
Some guest data on token
Request 2 (POST /login response):
User data merged with guest data on old token generating a new token
Procedural code example(you can do better =) ), you can run this on routes.php out of routes, I say that is ugly haha:
// ----------------------------------------------------------------
// AUTH TOKEN WORK
// ----------------------------------------------------------------
$authToken = null;
$getAuthToken = function() use ($authToken, $Response) {
if($authToken === null) {
$authToken = JWTAuth::parseToken();
}
return $authToken;
};
$getLoggedUser = function() use ($getAuthToken) {
return $getAuthToken()->authenticate();
};
$getAuthPayload = function() use ($getAuthToken) {
try {
return $getAuthToken()->getPayload();
} catch (Exception $e) {
return [];
}
};
$mountAuthPayload = function($customPayload) use ($getLoggedUser, $getAuthPayload) {
$currentPayload = [];
try {
$currentAuthPayload = $getAuthPayload();
if(count($currentAuthPayload)) {
$currentPayload = $currentAuthPayload->toArray();
}
try {
if($user = $getLoggedUser()) {
$currentPayload['user'] = $user;
}
$currentPayload['isGuest'] = false;
} catch (Exception $e) {
// is guest
}
} catch(Exception $e) {
// Impossible to parse token
}
foreach ($customPayload as $key => $value) {
$currentPayload[$key] = $value;
}
return $currentPayload;
};
// ----------------------------------------------------------------
// AUTH TOKEN PAYLOAD
// ----------------------------------------------------------------
try {
$getLoggedUser();
$payload = ['isGuest' => false];
} catch (Exception $e) {
$payload = ['isGuest' => true];
}
try {
$payload = $mountAuthPayload($payload);
} catch (Exception $e) {
// Make nothing cause token is invalid, expired, etc., or not exists.
// Like a guest session. Create a token without user data.
}
Some route(simple example to save user mobile device):
Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function () use ($getLoggedUser, $mountAuthPayload) {
Route::post('/session/device', function () use ($Response, $getLoggedUser, $mountAuthPayload) {
$Response = new \Illuminate\Http\Response();
$user = $getLoggedUser();
// code to save on database the user device from current "session"...
$payload = app('tymon.jwt.payload.factory')->make($mountAuthPayload(['device' => $user->device->last()->toArray()]));
$token = JWTAuth::encode($payload);
$Response->header('Authorization', 'Bearer ' . $token);
$responseContent = ['setted' => 'true'];
$Response->setContent($responseContent);
return $Response;
});
});
I've been Googling for quite some time now for some ideas or a guide on how to integrate OAuth (v1.0 & v2.0) alongside the standard Laravel 4 Eloquent authentication driver.
Essentially, I'd like to be able for site visitors to create an account via their existing Google, Facebook, or Twitter accounts, or via the standard email/password authentication. Certain user information such as email, first and last names, and avatar are important for me to have stored in a unified users table.
So far the projects I've looked at seem to support only OAuth and do away with the standard method. These projects include: eloquent-oauth, and oauth-4-laravel.
At this point, I might just roll my own solution, but I'm hoping some of you guys might have better advice for me!
TL;DR: I'm stuck at trying to find a simple and secure way to allow OAuth and standard Eloquent user authentication in Laravel. Halp.
I think the easiest way would be to use this service provider that you mention "artdarek/oauth-4-laravel" and then store the values as usual and log them in.
/**
* Login user with facebook
*
* #return void
*/
public function loginWithFacebook() {
// get data from input
$code = Input::get( 'code' );
// get fb service
$fb = OAuth::consumer( 'Facebook' );
// check if code is valid
// if code is provided get user data and sign in
if ( !empty( $code ) ) {
// This was a callback request from facebook, get the token
$token = $fb->requestAccessToken( $code );
// Send a request with it
$result = json_decode( $fb->request( '/me' ), true );
// store new user or login if user exists
$validator = Validator::make($result, array(
'email'=>'required|email|unique:users'
));
if ($validator->passes()) {
# Save user in DB
$user = new User;
$user->username = $result['name'];
$user->email = $result['email'];
$user->save();
}else {
$user = User::findByUsernameOrFail($result['name']);
}
Auth::login($user);
}
// if not ask for permission first
else {
// get fb authorization
$url = $fb->getAuthorizationUri();
// return to facebook login url
return Redirect::to( (string)$url );
}
}