PROBLEM: JWTAuth::attempt($credentials) is working in my windows local machine. When user calls /userLogin by providing credentials(username & password), it verifies the credentials and creates a token for the user. But same code: JWTAuth::attempt($credentials) is NOT working when I deploy the project on to Ubuntu Server.
DESCRIPTION:
I am using tymondesigns/jwt-auth for implementing JSON Web Tokens in my Laravel 5.1 project. I have built 2 REST APIs: /userRegister & /userLogin.
Here is code for /userRegister in UserController.php:
public function userRegister(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required',
'username' => 'required',
'password' => 'required',
'country' => 'required',
'zip_code' => 'required',
'date_time' => 'required',
'gender' => 'required',
]);
try
{
if ($validator->fails())
throw new Exception($validator->errors(), 409);
$name = $request->input('name');
$email = $request->input('email');
$username = $request->input('username');
$password = bcrypt($request->input('password'));
$country = $request->input('country');
$zip_code = $request->input('zip_code');
$fb_login = $request->input('fb_login');
$registration_date = $request->input('date_time');
$gender = $request->input('gender');
$user_email = UserProfile::where('email','=',$email)->first();
if($user_email!=null)
{
throw new Exception("User with this email is already registered");
}
$check_username = UserProfile::where('username','=', $username)->first();
if ($check_username != null)
{
throw new Exception("User with this username is already registered. Please use different username");
}
$saveUser = new UserProfile();
$saveUser->name=$name;
$saveUser->email=$email;
$saveUser->username=$username;
$saveUser->password=bcrypt($password);
$saveUser->country=$country;
$saveUser->zipcode=$zip_code;
$saveUser->gender=$gender;
$saveUser->fb_login=$fb_login;
$saveUser->registration_date=$registration_date;
$build_trial_date = new Carbon($registration_date);
$trial_end_date = $build_trial_date->addDays(30);
$saveUser->trial_end_date=$trial_end_date;
$result = $saveUser->save();
if (!$result)
throw new Exception("Error in registration. Please try again.");
$user_id = $saveUser->id;
$loginUser = UserProfile::find($user_id);
$loginUser->update(['logged_in' => 1]);
return ResponseService::buildSuccessResponse("User registered successfully");
}
catch(Exception $e)
{
$data = [
'error' => true,
'result' => [
'status_code' => $e->getCode(),
'message' => $e->getMessage(),
]
];
$result = ResponseService::buildFailureResponse($data);
return $result;
}
}
As you can notice I am using bcrypt() for before saving password.
Now, here is code for /userLogin in UserController:
public function userLogin(Request $request)
{
// grab credentials from the request
$credentials = $request->only('username', 'password');
Log::info("username and password obtained from Request: ".json_encode($credentials));
try
{
if(JWTAuth::attempt($credentials)) // For Logging
{
Log::info("$ token = JWTAuth::attempt $ credentials Result: TRUE");
}else{
Log::info("$ token = JWTAuth::attempt $ credentials Result: FALSE");
}
// attempt to 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 whilst attempting to encode the token
return response()->json(['error' => 'could_not_create_token'], 500);
}
Log::info("Generated token is: ".json_encode(compact('token')));
// all good so return the token
return response()->json(compact('token'));
}
/userRegistration Api is working both in my windows local m/c as well
as in Ubuntu server. Also bcrypt() on password is working in
it.
BUT, /userLogin is working in my windows local m/c but not working in
Ubuntu Server.
Help me solve this silent-dead-hidden error. TIA.
FURTHER DETAILS:
I have also cross-checked jwt.php file both in my windows and ubuntu server. They are having same configuratons. Also I have used php artisan jwt:generate on ubuntu server.
I finally could solve the problem when I went through the code again. Luckily I could noice that! Here is the mistake I did. At some point in time I have added this code bcrypt() like this:
$password = bcrypt($request->input('password'));
But I was already using bcrypt() before saving $password into the database.
$saveUser = new UserProfile();
$saveUser->password=bcrypt($password);
Since it was encrypting twice before saving to database, when I called /userLogin api, JWTAuth::attempt($credentials) was checking for credintials and it was not able to validate. So I removed bcrypt() at one place and now its working fine. [Solved].
Related
I'm trying to implement SAML authentication against Azure AD in my Laravel application using aacotroneo/laravel-saml2 package.
I configured the IDP settings and the microsoft login page appears correctly as it should be.
The problem is that I'm not able to take user informations from Azure.
I followed the instructions found in the answer of this question Azure Active Directory SSO with Laravel
I changed the code inside the file app/Providers/SAML2ServiceProvider.php adding these lines:
public function boot()
{
Event::listen('Aacotroneo\Saml2\Events\Saml2LogoutEvent', function ($event) {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
unset($_SESSION["id"]);
session_destroy();
});
Event::listen('Aacotroneo\Saml2\Events\Saml2LoginEvent', function (Saml2LoginEvent $event) {
$messageId = $event->getSaml2Auth()->getLastMessageId();
// Add your own code preventing reuse of a $messageId to stop replay attacks
if (session_status() == PHP_SESSION_ACTIVE) {
session_start();
}
$user = $event->getSaml2User();
Log::info("COOKIE_SAML ACTIVATED");
$_COOKIE["COOKIE_SAML"] = 1;
$userData = [
'id' => $user->getUserId(),
'attributes' => $user->getAttributes(),
'assertion' => $user->getRawSamlAssertion()
];
Log::info(json_encode($userData));
$inputs = [
'sso_user_id' => self::getValue($user->getUserId()),
// 'username' => $user->getAttribute('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'),
'email' => self::getValue($user->getAttribute('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name')),
'first_name' => self::getValue($user->getAttribute('http://schemas.microsoft.com/identity/claims/displayname')),
'last_name' => self::getValue($user->getAttribute('http://schemas.microsoft.com/identity/claims/displayname')),
];
$user = UserAdecco::where('sso_user_id', $inputs['sso_user_id'])->where('email', $inputs['email'])->first();
if (!$user) {
$res = UserAdecco::store($inputs);
if ($res['status'] == 'success') {
$user = $res['data'];
$_SESSION["id"] = $user->id;
// Auth::guard('web')->login($user);
} else {
Log::info('SAML USER Error ' . $res['messages']);
}
} else {
$_SESSION["id"] = $user->id;
// Auth::guard('web')->login($user);
}
});
}
But it seems that this code is not executed at all. The result is that when I login nothing happens and the user is not logged inside the Laravel application.
What am I missing?
I found the problem, the service provider needs to be registered inside config/app.php to work.
I've been trying to create a laravel login with users from a drupal 7 database. Since drupal 7 has his own encription method for passwords, the ones that come with laravel don't work, hence whenever there's a try to log in it always returns authentication failure.
{
$request->validate([
'mail' => 'required|string|email',
'pass' => 'required|string',
'remember_me' => 'boolean',
]);
//dd(hash('sha256',$request->pass));
$credentials = request(['mail', 'pass']);
if (!Auth::attempt(['mail' =>$request->mail, 'pass' => hash('sha256',$request->pass)])) {
return response()->json([
'message' => 'Unauthorized'], 401);
}
$user = $request->user();
$tokenResult = $user->createToken('Personal Access Token');
$token = $tokenResult->token;
if ($request->remember_me) {
$token->expires_at = Carbon::now()->addWeeks(1);
}
$token->save();
$user->api_token = $tokenResult->accessToken;
return response()->json($user);
}
This is my login method in the controller.
I don't have access at all to the drupal project.
I'm open for any idea.
If you navigate to includes/password.inc in your drupal directory, you will find a function named: user_check_password. This method can help you verify your plain text password against the hashed password.
So, the easiest way would be connecting laravel to the existing drupal database and make use of the password.inc file from drupal.
I am creating an API for registration and authentication by Active Directory.
When registering, I use the "userPassword" field to store the password.
$user = new User([
'cn' => $full_name,
'userPassword' => $password,
'samaccountname' => $username,
'mail' => $email
]);
$user->inside(config('ldap.connections.default.base_dn'));
$user->save();
Registration works fine.
But at the time of authentication, the Active Directory does not recognize the provided password:
$user = User::where('samaccountname', '=', $username)->firstOrFail(); // all is good here. print_r($user) shows user details
if (Container::getDefaultConnection()->auth()->attempt($user['distinguishedname'], $password)) {
// returns a response to the user
return \response()->json(
new UserResource($user), 200
);
} else {
return \response()->json(
[
'message' => 'invalid credentials or user not exists.'
], 403
);
}
Can someone help me.
Using plain stored password won't work, there are some encoding which need to be carried out but there's a mutator unicodePwd that will handle it for you. Try
$user = new User([
'cn' => $full_name,
'unicodePwd ' => $password,
'samaccountname' => $username,
'mail' => $email
]);
The attribute used to set the password of an account is unicodePwd. This attribute can only be set, not read.
There are some strict requirements about the format of what you put there, which are described in the Microsoft documentation. However, the LdapRecord library handles that for you, according to their documentation:
The password string you set on the users unicodePwd attribute is automatically encoded, you do not need to encode it yourself.
Their example on how to create an AD user looks like this:
$user = (new User)->inside('ou=Users,dc=local,dc=com');
$user->cn = 'John Doe';
$user->samaccountname = 'jdoe';
$user->unicodePwd = 'SecretPassword';
$user->userPrincipalName = 'jdoe#acme.org';
$user->save();
// Enable the user.
$user->userAccountControl = 512;
try {
$user->save();
} catch (\LdapRecord\LdapRecordException $e) {
// Failed saving user.
}
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());
}
});
So I am developing Ionic app with Laravel back-end and using JWT authentication.
My question is...since im using 4 fields when registering a user, and only 2 when logging in (email and pass), I suppose that upon registration the token should be made of only those 2 fields...
This is the working sign up function:
public function signUp()
{
$credentials = Input::all();
if (User::whereEmail($credentials['email'])->first()) {
return Response::json([
'error' => 'User with given e-mail already exists',
], 409);
} elseif (User::wherePhone($credentials['phone'])->first()) {
return Response::json([
'error' => 'User with given phone number already exists',
], 409);
} else {
$user = User::create($credentials);
$token = JWTAuth::fromUser($user);
return Response::json(compact('token'));
}
}
However if I change $credentials = Input::only('email', 'password') the full user won't be created (since there are fields missing).
But even if I leave $credentials as-is, and make combinations like
$token = JWTAuth::fromUser(Input::only('email', 'password')) or parse e-mail and password to JSON, or something similar...I get a "Trying to get a property of non-object" error, or that array is given instead of an object to JWTAuth...
JWTAuth::fromUser(Input::only('email', 'password')) expects a User object.
If you wish to use credentials you can do something like this:
// grab credentials from the request
$credentials = Input::only('email', 'password');
try {
// attempt to 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 whilst attempting to encode the token
return Response::json(['error' => 'could_not_create_token'], 500);
}
// all good so return the token
return Response::json(compact('token'));
https://github.com/tymondesigns/jwt-auth/wiki/Creating-Tokens