Laravel Email Verification 5.8 with sqlsrv - laravel

I implemented the email verification that Laravel offers out of the box but currently have an issue when one click on the verification link sent on email. It brings the error: Data Missing
This error is throw by Carbon at the point when the column email_verified_at is being updated, and this was only on the SQLSRV implementation. I switched to a test MySQL database and this worked, although I need it to work with SQLSRV. I have not checked to see if any other database implementation encounters this problem.

Add it into app\Http\Controller\Auth\VerificationController.php
protected function getDateFormat()
{
return 'Y-m-d H:i:s';
}

Related

Laravel6 - Email verification customization on multi-login platform

I am new to laravel. I am trying to implement email verification on my laravel6 project.
On my project, there're three user types named 'user' 'vendor' and 'admin'. I have prepared separate directories for each user type in 'Controllers' and each of them has their own Auth files (e.g. Directory 'App/Http/Controllers/Vendor/Auth' has its own VerificationController.php, etc). So far, I've successfully implemented registration and login/logout function for each type with separate table in my DB.
A issue popped up when I tried to implement email verification. When I tried to access to a page where email verification is required, 'Auth\VerificationController#show' method seemed to be called regardless of the user type.
I went over laravel source code and learned that within the process, router calls Illuminate/Routing/Router->emailVerification() method. And the emailVerification() method routes to 'Auth\VerificationController#show' regardless of the user type.
What I wanted to do is to route depending on user type (e.g. if 'vendor' tries to login, I want to call 'Vendor\Auth\VerificationController#show').
I don't come up with any idea how to do for that. Can anyone please give me any advice?
Illuminate\Routing\Router class
public function emailVerification()
{
$this->get('email/verify', 'Auth\VerificationController#show')->name('verification.notice');
$this->get('email/verify/{id}/{hash}', 'Auth\VerificationController#verify')->name('verification.verify');
$this->post('email/resend', 'Auth\VerificationController#resend')->name('verification.resend');
}
Thank you in advance.

Using Laravel Test 7 and Laravel Passport 9.3 with Personal Access Client gives exception "Trying to get property 'id' of non-object"

I am designing a custom authentication scheme (based on public keys) alongside a stateless API, and decided Passport would fulfill the need for post-authentication requests.
Assuming the authentication succeeds, and the user is authenticated, they would receive a Personal Access Token, and use the token for all further requests. The trouble I'm experiencing (still after much searching through various forums and Stack Overflow) is that when using Laravel's built in testing suite, on the createToken() method, it generates an (admittedly common) exception:
"ErrorException : Trying to get property 'id' of non-object".
I am able to manually create a user through Tinker, and create a token through Tinker. However I'm experiencing problems when attempting to automate this process after authenticating.
Here is the relevant code snippet post-authentication:
Auth::login($user);
$user = Auth::user();
$tokenResult = $user->createToken('Personal Access Token');
$token = $tokenResult->token;
$token->expires_at = Carbon::now()->addWeeks(1);
$token->save();
return response()->json([
"access_token" => $tokenResult->accessToken,
"token_type" => "Bearer",
"expires_at" => Carbon::parse(
$tokenResult->token->expires_at)->toDateTimeString()
],
200);
I've manually called Auth::login on the user, to ensure the user is logged in, and Auth::user() returns the user (not null). Upon executing the third line of code, the exception is thrown with the following mini stack-trace (I can provide a full stack-trace if requested).
laravel\passport\src\PersonalAccessTokenFactory.php:100
laravel\passport\src\PersonalAccessTokenFactory.php:71
laravel\passport\src\HasApiTokens.php:67
app\Http\Controllers\Auth\LoginController.php:97
laravel\framework\src\Illuminate\Routing\Controller.php:54
laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php:45
From running this through debug a few times- even though the class is called and loaded, and it appears the Client is found through ControllerDispatcher -> Client::find(id) and found in ClientRepository, when it gets to PersonalAccessTokenFactory, the $client passed in is null (which explains why the $client->id can't be found, though I have no idea why the $client is null at this point).
protected function createRequest($client, $userId, array $scopes)
{
$secret = Passport::$hashesClientSecrets ? Passport::$personalAccessClientSecret : $client->secret;
return (new ServerRequest)->withParsedBody([
'grant_type' => 'personal_access',
'client_id' => $client->id,
...
}
Things I have done/tried with some guidance from the documentation and other posts:
Manually created a user in Tinker, and created the token through Tinker- this does work.
Ensured the user is logged in before attempting to generate token.
passport:install (and adding the --force option)
Ensured Personal Access Client is generated with passport:client --personal
Ensured the AuthServiceProvider::boot() contains the ClientID and Client Secret (in the .env).
migrate:refresh followed by passport:install --force
Complete removal of Passport, removing all files, keys, migrations, and DB entries, followed with a migrate:refresh and reinstallation of Passport, along with generating an additional personal access client (even though one is generated during passport:install).
I'm not sure where else to look/what else to try at this point, so any help or guidance would be much appreciated!
I eventually discovered the solution. The problem is multi-layered, in part having to do with outdated Laravel documentation in regards to testing and Passport Personal Access Clients.
The first part of the problem had to do with using the trait RefreshDatabase on my unit test. Since this creates a mock database with empty datasets, although the clients themselves exist in the real database and the .env file, when the test is run, the test does not see those clients as existing in the mock database. To solve this problem, you must create a client in the setup function before the test is run.
public function setUp() : void
{
parent::setUp();
$this->createClient(); //Private method->Full code below
}
This solves the issue about having a null client during testing, but starting in Laravel 7, Laravel added a requirement for Personal Access Clients that the id and the client secret has to be kept inside the .env file. When running the test, the test will see the actual client id and secret in the .env, and fail to validate these with the client that was created and stored in the mock database, returning another exception: "Client Authentication Failed".
The solution to this problem is to create a .env.testing file in your main project directory, copying your .env file contents to it and ensuring that the keys below exist with values for either your main created Personal Access Client, or copying the secret from a client generated just for testing (I would advise the latter).
PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=unhashed-client-secret-value
Then using the code below, make sure the $clientSecret value is the same as the key value in your .env.testing file.
private function createClient() : void
{
$clientRepository = new ClientRepository();
$client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', 'http://localhost'
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $client->id,
'created_at' => new DateTime,
'updated_at' => new DateTime,
]);
$clientSecret = 'unhashed-client-secret-value';
$client->setSecretAttribute($clientSecret);
$client->save();
}
This will create a new client, set the attribute secret to the value in the variable and update the mock database secret to contain the same value. Hopefully this helps anyone with the same issue.
Another way to prevent copy/paste source code is to just call artisan command in the setup method.
public function setUp() {
parent::setUp();
$this->artisan('passport:install');
}
original here
Just use the facade
public function setUp() {
parent::setUp();
Artisan::call('passport:install');}

Laravel - How to create authentication for API without database

I'm writing an app at the moment which makes use of web-sockets and therefore needs to keep track of its users somehow.
I don't really want my users to register. Before using the app they should choose a name and will get a JWT-Token for it. I don't want to save anything in a database. As these names can be non-unique I will probaply add an Id.
I'm trying to use tymon/jwt-auth": "^1.0.0-rc.3.
public function login()
{
$token = auth()->tokenById(1234));
return $this->respondWithToken($token);
}
For some reason the tokenById-Function seems to not be available.
Postman says: BadMethodCallException: Method Illuminate\Auth\SessionGuard::tokenById does not exist.
In my case i have clear the cache. Then its working fine

Upgrade to Laravel 5.3 - Mail::send now returns null from Mandrill

I'm upgrading from Laravel 5.2 to 5.3 but since doing so the Mail::send function no returns null when sending via Mandrill.
I had the same issue in Laravel 4.2, but it seems this functionality was then added when upgrading to version 5.
$response = Mail::send($template, $email, function($message) use($subject)
{
$message->to('test#example.com')->subject($subject);
});
dd($response)
Message sends fine, but the response is null where is gave the message ID/content in 5.1 & 5.2 before the upgrade
I can't understand why they would add the feature then remove it again?
This is because the syntax has fundamentally changed. You need to use the Mailable Class rather than a callback.
Your code should look something like this:
Mail::to('test#example.com')->send(new EmailExample($data));
Where EmailExample is a class that extends Mailable and handles your email body and any applicable logic.
See the docs here for further information

Laravel 5.7 email verification throws 403

I implemented email verification in a Laravel 5.7 project I'm working on. I get the email, but whenever I click on the confirm button or even the url provided in the email, I get a 403 forbidden error.
I have searched for several solutions, but haven't been able to find one to this problem. The only reasonable pointers to this error is this github issue https://github.com/laravel/framework/issues/25716 which has been merged and closed by Taylor Otwell by still this problem persists.
Here's the email I get:
Here's the error it throws when I click on the button or the actionUrl at the email footer: and here's the url shown when the 403 page is displayed https://www.mywebsite.com/email/verify/1?expires=1540140119&signature=fd7dc72b05da6f387b2f52a27bceee533b2256436f211930c1319c7a544067da
Please help me. Thank you
Edits: This problem occurs only in production app. On local, this email verification works but throws 403 on production(live) server. My email service is mailgun, and I can access every other email contents relating to the app except completing email verification.
I need help please. Thanks in anticipation
One of the reasons that was in my case can be that you are already logged in with a normal verified user, and you have clicked on the verification email link. In that case it will shoot 403 . Which is not normal in my opinion, but whatever.
For me because manually create verification route. which in laravel 6.x or 7.x The route path for verifying emails has changed. from /email/verify/{id} to /email/verify/{id}/{hash} This probably only happens because I use the rules manually, and not Auth::routes(['verify' => true])
for more information laravel upgrade guide upgrade#email-verification-route-change
This typically occurs if your application is running behind some proxies and probably doesn't handle SSL termination itself.
The solution is to add
protected $proxies = '*';
to the TrustProxies middleware.
Reference: https://laracasts.com/discuss/channels/laravel/hitting-403-page-when-clicking-verify-link-in-email-using-new-laravel-verification-57?page=1
Turns out, this often happens when you have your laravel app running behind a proxy (apache, nginx etc.) We therefore end up replacing laravel's default 'signed' middleware with our own middleware that checks for https:// links. This StackOverFlow answer here was able to fix this problem for me:
Signed route for email verification does not pass signature validation
To use Laravel email verification you must first add the proper routes.
If you take a look at Illuminate/Routing/Router.php you'll see that by default the verify route is disabled.
if($options['verify'] ?? false)
{
$this->emailVerification();
}
To enable your verification routes add the following to your web.php
Auth::routes(['verify'=>true]);
Then run
php artisan route:list
to make sure that it's working.
Check the verify method inside the VerifiesEmails trait,
there they have:
if (! hash_equals((string) $request->route('hash'), sha1($request->user()->getEmailForVerification()))) {
throw new AuthorizationException;
}
I have dumped this variable $request->route('hash') and it was null, so I overrided it in the VerificationController:
/**
* Mark the authenticated user's email address as verified.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function verify(Request $request)
{
if (! hash_equals((string) $request->route('id'), (string) $request->user()->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->query('hash'), sha1($request->user()->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath());
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('verified', true);
}
And now it works!
The problem for me was my APP_URL had a protocol of http and when I clicked on the verification link NGINX automatically redirected the url from http to https that's why the signature validation failed. I updated the APP_URL to have a protocol of https and that resolved my problem.
My personal experience with this problem was that I set MAIL_DRIVER to log in the .env file, and Laravel escaped special characters (such as &) when it stored the activation link in the log.
So NEVER use the log for MAIL_DRIVER when you have verification email.
(my Laravel version was 5.8).

Resources