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)"
Related
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
So, I'm using Laravel+Passport and so far is working fine.
But, I would like to made a small change to the passport code(well, not in the vendor folder, I hope), once that I would request the User to change it's password in case that he is doing the first login.
So, what I would need is two things (I believe):
1 - How can I add one more info to the oauth/token response? Together with the access_token, I would like to add one column from the DB that is needsNewPassword=true/false.
2 - In case that needsNewPassword is true, then, the app will redirect to another screen, where the user will set a new password. I would set the new password, remove the flag for needsNewPassword and send back a new access_token to the user. The user then, would use only that access_token. How can I regenerate a new access_token?
Thanks for you help! João
Right,
I answering my own question, in case someone needs to do the same. Maybe is not the best way, but is working.What I did is:
Create a new route, like /api/login that points to a method (be sure that is Outside of your middleware "auth", once that it's not sending the token in thi call). E.g: Route::post('/login', 'Auth\LoginController#apiLogin');
in the method, you do a request to the oauth/token and, with the result, you add the fields that you want.
test
function apiLogin(Request $request) {
$tokenRequest = $request->create('/oauth/token', 'POST', $request->all());
$request->request->add([
"client_id" => 'your_client_id',
"client_secret" => 'your_client_secret',
"grant_type" => 'password',
"code" => '*',
]);
$response = Route::dispatch($tokenRequest);
$json = (array) json_decode($response->getContent());
$json['new_value'] = '123456';
$response->setContent(json_encode($json));
return $response
}
This is working for me. In my case, I also have just one app so, my client_id, client_secret, grant_type and code is added in the server side. The client only need to pass username(or email, depends of what you are using) and password and then it will get the access_token and the other info that I want to send as well.
Hope that this helps someone else too.
Cheers,
joao
#joao.sauer
Your own answer is working like a charm, but if you wan't a bit more freedom, you could extend Passport's own AccessTokenController.
A simple example:
use App\Models\User;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ServerRequestInterface;
use Response;
class AccessTokenController extends \Laravel\Passport\Http\Controllers\AccessTokenController
{
public function issueToken(ServerRequestInterface $request)
{
try {
//get username (default is :email)
$username = $request->getParsedBody()['username'];
//get user
$user = User::where('email', '=', $username)->firstOrFail();
//issuetoken
$tokenResponse = parent::issueToken($request);
//convert response to json string
$content = $tokenResponse->getBody()->__toString();
//convert json to array
$data = json_decode($content, true);
if(isset($data["error"]))
throw new OAuthServerException('The user credentials were incorrect.', 6, 'invalid_credentials', 401);
//add access token to user
$user = collect($user);
$user->put('access_token', $data['access_token']);
return Response::json(array($user));
}
catch (ModelNotFoundException $e) { // email notfound
//return error message
}
catch (OAuthServerException $e) { //password not correct..token not granted
//return error message
}
catch (Exception $e) {
////return error message
}
}
}
credits to Messi89:
Laravel Passport - Customize The Token Response
I found a simple solution without need new request, controller or extends, just add parameters to request and call issueToken via app, it can useful for starter:
// in routes/api.php
Route::post('/token',function(Request $request){
$request->request->add([
'grant_type' => 'password',
'client_id' => '2',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
]);
return app()->call('\Laravel\Passport\Http\Controllers\AccessTokenController#issueToken');
});
Also can add try...catch block to handle exceptions or add parameters to response before send to client
Route::post('/token',function(Request $request){
$request->request->add([
'grant_type' => 'password',
'client_id' => '2',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
]);
try {
$response = app()->call('\Laravel\Passport\Http\Controllers\AccessTokenController#issueToken');
$newResponse = json_decode($response->content());
// Add parameters to response here
$newResponse->user = ['user'=>'user','pass'=>'pass'];
return Response()->json($newResponse);
}catch (Laravel\Passport\Exceptions\OAuthServerException $e) {
if ($e->statusCode() == 400) {
return response()->json(['message' => 'Invalid request. Please enter username and password.'], $e->statusCode());
} else if ($e->statusCode() == 401) {
return response()->json(['message' => 'Your credentials are incorrect. Please try again.'], $e->statusCode());
}
return response()->json('Something went wrong on the server. Please try later.', $e->statusCode());
}
});
I'm trying to learn Laravel and my goal is to be able to build a RESTful API (no use of views or blade, only JSON results. Later, an AngularJS web app and a Cordova hybrid mobile app will consume this api.
After some research, I'm inclining to choose JWT-Auth library for completely stateless benefit. My problem is: I have 2 main types of users: customers and moderators. Customers are not required to have a password. I need to be able to generate a token for access with the provided email only. If that email exists in the database and it belongs to a customer, it will generate and return the token.
If it exists and belongs to a moderator, it will return false so the interface can request a password. If the email doesn't exist, it throws an invalid parameter error.
I read the docs here and it says it's possible to use Custom Claims. But the docs doesn't explain what are claims and what it means the array being passed as custom claims. I'd like some input on how to go about achieving what I explain above.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
class AuthenticateController extends Controller
{
public function authenticate(Request $request)
{
$credentials = $request->only('email', 'password');
try {
// verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong
return response()->json(['error' => 'could_not_create_token'], 500);
}
// if no errors are encountered we can return a JWT
return response()->json(compact('token'));
}
}
Thanks you.
Update
Bounty's code
public function authenticate(Request $request) {
$email = $request->input('email');
$user = User::where('email', '=', $email)->first();
try {
// verify the credentials and create a token for the user
if (! $token = JWTAuth::fromUser($user)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong
return response()->json(['error' => 'could_not_create_token'], 500);
}
// if no errors are encountered we can return a JWT
return response()->json(compact('token'));
}
try with this:
$user=User::where('email','=','user2#gmail.com')->first();
if (!$userToken=JWTAuth::fromUser($user)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
return response()->json(compact('userToken'));
it works for me, hope can help
Generating token for the customers (without password) can be achieved through
$user = \App\Modules\User\Models\UserModel::whereEmail('xyz#gmail.com')->first();
$userToken=JWTAuth::fromUser($user);
Here $userToken
will stores the token after existence check of email in the table configured in UserModel file.
I have assumed that you stores both customer and moderators in the same table, there must be some flag to discriminate among them. Assume the flag is user_type
$token = null;
$user = \App\Modules\User\Models\UserModel::whereEmail('xyz#gmail.com')->first();
if($user['user_type'] == 'customer'){
$credentials = $request->only('email');
$token =JWTAuth::fromUser($user);
}else if($user['user_type'] == 'moderator'){
$credentials = $request->only('email','password');
$token = JWTAuth::attempt($credentials);
}else{
//No such user exists
}
return $token;
As far as custom claims are concerned these are custom defined payloads which can be attached to token string.
For example, JWTAuth::attempt($credentials,['role'=>1]); Will attempt to add role object to token payload.
Once you decode the token string through JWT Facade JWTAuth::parseToken()->getPayload(); you in turn get all payloads defined in required_claims under config/jwt.php with additional role payload.
Refer https://github.com/tymondesigns/jwt-auth/wiki/Creating-Tokens#creating-a-token-based-on-anything-you-like
Let me know in case you requires anything else.
Rather than making a different login strategy for customers and moderators, you can add token authentication to both user type. this will makes your life easier and prepare for scalability.
In your api, you can just restrict moderator users to not have access to the api by sending
<?php
Response::json('error'=>'method not allowed')
Apart from this suggestion, I believe #Alimnjan code should work.
If you don't already have an App\User object, get it with something like
$user = App\User::find(1);
Generate the token using the fromUser() method of JWTAuth
$token = \JWTAuth::fromUser($user)
The above doesn't authenticate the user, it only generates a JWT token. If you need to authenticate the user, then you have to add something like this
\JWTAuth::setToken($token)->toUser();
I am following the docs from link below:
https://developers.google.com/+/mobile/android/sign-in#enable_server-side_api_access_for_your_app
Specifically the part that says:
If you do not require offline access, you can retrieve the access token and send it to your server over a secure connection. You can obtain the access token directly using GoogleAuthUtil.getToken() by specifying the scopes without your server's OAuth 2.0 client ID. For example:
I retrieve the access token like this:
accessToken = GoogleAuthUtil.getToken(
AuthenticatorActivity.this,
Plus.AccountApi.getAccountName(Common.mGoogleApiClient),
"oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/plus.login email"
);
After I retrieve the access token I send it to a web server, on the web server i can see that it's a valid access token by calling
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.$_POST['google_access_token']
The request above returns the android apps client id, it also returns the users email correctly.
The problem is that when I try to run $client->authenticate($_POST['google_access_token']); I get an exception with the message: "invalid_grant: Incorrect token type".
To prevent getToken caching I always invalidate the token in android app:
if (accessToken != null && !accessToken.isEmpty()) {
GoogleAuthUtil.invalidateToken(AuthenticatorActivity.this, accessToken);
}
Here's the php code:
if (!isset($_POST['google_access_token'])) {
throw new Exception('missing google_access_token');
}
$client = new \Google_Client();
$client->setApplicationName("GiverHub");
$client->setClientId($this->config->item('google_client_id'));
$client->setClientSecret($this->config->item('google_client_secret'));
$client->setDeveloperKey($this->config->item('google_developer_key'));
$client->setRedirectUri($this->config->item('google_redirect_uri'));
$client->setScopes([
'https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/plus.me',
'email',
]);
try {
$client->authenticate($_POST['google_access_token']); // if i remove this the rest of the code below works! ...
$reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.$_POST['google_access_token'];
$req = new \Google_Http_Request($reqUrl);
$io = $client->getIo();
$response = $io->executeRequest($req);
$response = $response[0];
$response = json_decode($response, true);
if ($response === null) {
throw new Exception('Failed to check token. response null');
}
if ($response['issued_to'] !== '466530377541-s7cfm34jpf818gbr0547pndpq9songkg.apps.googleusercontent.com') {
throw new Exception('Invalid access token. issued to wrong client id: '. print_r($response, true));
}
if (!isset($response['user_id'])) {
throw new Exception('Missing user_id');
}
if (!isset($response['email'])) {
throw new Exception('Missing email');
}
/** #var \Entity\User $user */
$user = Common::create_member_google([
'id' => $response['user_id'],
'email' => $response['email'],
'given_name' => '',
'family_name' => '',
]);
$user->login($this->session);
if ($user instanceof \Entity\User) {
echo json_encode( [ 'success' => true, 'user' => $user ] );
} else {
echo json_encode( [ 'success' => false, 'msg' => $user ] );
}
} catch(Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
The above code works if i remove the $client->authenticate(); line ... The problem is that I can't get the given_name / family_name etc .. only email / google_user_id from the tokeninfo ...
Any thoughts about why the key works for tokeninfo but not for authenticate?
I have tried many different variations of the scopes .. both on the server side and the android side ..
The $client->authenticate() method doesn't quite do what you're trying to do. It takes a one-time code from an earlier OAuth transaction and exchanges it for the access token. In your case - you're saying you already have the access token.
You should be able to call $client->setAccessToken() to set the token instead, so it may look something like
$client->setAccessToken($_POST['google_access_token']);
This is the solution I came up with after user158443 suggested I use $client->setAccessToken();
// first json_encode the access token before sending it to $client->setAccessToken();
$json_encoded_access_token = json_encode([
'access_token' => $_POST['google_access_token'],
'created' => time(), // make up values for these.. otherwise the client thinks the token has expired..
'expires_in' => time()+60 // made up a value in the future...
]);
// and then set it
$client->setAccessToken($json_encoded_access_token);
// and then get userinfo or whatever you want from google api !! :)
$oauth2 = new \Google_Service_Oauth2($client);
$user_info = $oauth2->userinfo->get();
NOTE: it's probably not smart to "emulate" the expires_in and created that i just did if you are in production ... You should probably call tokeninfo first and get the expires time from there...
NOTE: I still have no idea how to get a refresh token for this... but I don't need one for my use case..
I am trying to create a new listing on Etsy.
I used oauth to authenticate and got
OAUTH_CONSUMER_KEY
and
OAUTH_CONSUMER_SECRET
I check it with this code and I got return og all the seller data, so everything is ok with the OAuth.
$oauth = new OAuth(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI);
$oauth->setToken("key","secret");
try {
$data = $oauth->fetch("http://openapi.etsy.com/v2/users/__SELF__", null, OAUTH_HTTP_METHOD_GET);
$json = $oauth->getLastResponse();
print_r(json_decode($json, true));
} catch (OAuthException $e) {
error_log($e->getMessage());
error_log(print_r($oauth->getLastResponse(), true));
error_log(print_r($oauth->getLastResponseInfo(), true));
exit;
}
I am trying to crate a new listings. First i managed to create a new listing through the api browser on the production. Now, i want to create a new listing through PHP. This is what i did, and it return my error:
This is my code:
$oauth = new OAuth(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET,OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI);
$oauth->setToken("key","secret");
try {
$url = "http://openapi.etsy.com/v2/listings";
$params = array('description' => 'thisisdesc','materials' => 'yes','price'=>"5.99"
,'quantity'=>"2",'shipping_template_id'=>"52299",'shop_section_id'=>"1"
,'title'=>"thisistitle",'category_id'=>"i_did",'who_made'=>"5.99"
,'is_supply'=>"1",'when_made'=>"2010_2012");
$oauth->fetch($url, $params, OAUTH_HTTP_METHOD_POST);
print_r(json_decode($json, true));
} catch (OAuthException $e) {
print_r($e);
error_log($e->getMessage());
error_log(print_r($oauth->getLastResponse(), true));
error_log(print_r($oauth->getLastResponseInfo(), true));
exit;
}
I get the response of:
Invalid auth/bad request (got a 403, expected HTTP/1.1 20X or a redirect)
This method not accessible to restricted API key.
If you are still developing your application you can create listings without gaining full API access, so long as the shop is on your account. Make sure you parse in listings_w as a permission scope when making the first OAUTH request. I have altered the example provided by ETSY in the code below.
// instantiate the OAuth object
// OAUTH_CONSUMER_KEY and OAUTH_CONSUMER_SECRET are constants holding your key and secret
// and are always used when instantiating the OAuth object
$oauth = new OAuth(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET);
// make an API request for your temporary credentials
$req_token = $oauth->getRequestToken("https://openapi.etsy.com/v2/oaut/request_token?scope=email_r%20listings_r%20listings_w", 'oob');
print $req_token['login_url']."\n";`
Notice the scope=email_r%20listings_r%20listings_w;
Hope this helps
Aha, here's the answer. From an Etsy developer:
Your API was not yet approved for full API access. I've fixed that, so you should be able to use those methods now.
Hence, get in touch with the firm, and ask for your key to be approved.