Eloquent queries without database (data is retrieved from external API) - php

I have a Laravel web application (website.nl) AND an Laravel API (api.website.nl).
When the user logs in with the login form on the website. A post request is done to the API. When the login request is successfull a token AND User object is send back in the form of a JSON string. The login method looks like this.
public function login(Request $request)
{
$email = $request->input('email');
$password = $request->input('password');
$response = $this->client->request('POST', self::$apiRootUrl . '/login', [
'headers' => [
'Accept' => 'application/json',
],
'form_params' => [
'email' => $email,
'password' => $password
],
]);
$result = json_decode($response->getBody()->getContents());
if (isset($result->success->token))
{
$user = User::create([
'id' => $result->success->user->id,
'name' => $result->success->user->name,
'email' => $result->success->user->email,
'password' => $password
]);
Auth::login($user);
$this->apiToken = $result->success->token;
return $this->sendLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
Where I want this login function to be as stupid as possible, meaning, let the API figure out if this is a valid login request. This method only passes the arguments.
The main website should not have a database. It only uses the API to login, get a token, and do requests with that token to gather data for the user to see on the main website's dashboard.
For this reason my question is this: How can I store the User object retrieved from the API in such a manner that it will be query-able like an Eloquent model. But without the need to store it in a database. Again, the main website shouldn't need a database. It only communicates with the API for it's data.
I hope this question makes sense, if not, please ask for more details.
I don't want to create a duplicate database with migrations on the main website. Because that's the job of the .api subdomain. Also, all the models are used within a session. Never being stored for a longer time, because that's also located in a store method on the .api subdomain.

This would require you to write your own grammar for Eloquent, an ActiveRecord implementation for the API you're using.
In theory it's possible, in practice it's a lot of work.
Take a look here for how Laravel implements the database types it currently supports.
https://laravel.com/api/5.8/Illuminate/Database/Schema/Grammars.html

Related

Un-signing/Decoding JWTs from internal API

I'm modifying our laravel site to authenticate users through our own internal API rather than just against passwords in the database.
When the API is called, and the user is verified as existing, it returns multiple JWTs with their own scopes. I need to un-sign, decode and cache those.
My issue is that I've looked into packages for laravel to do this with JWTs and even installed the Tymon library to look into it, but that seems to be more about issuing the tokens through Laravel.
I have this login function that, once the user signs in, dumps the encoded tokens on the login page view:
public function login(Request $request)
{
$this->validate($request, [
'email' => 'required',
'password' => 'required',
]);
$credentials = $request->only('email', 'password');
$email = $request->input('email');
$password = $request->input('password');
$authService = new AuthService();
$login = $authService->loginGetToken($email, $password);
dd($login);
//var_dump($login); //this dumps upon 'sign in' then redirectes to login page
return redirect(route('auth.login'))
->with('login', $login);
}
So I've successfully returned my multiple JWTs from the API, but what is the best way to go about un-signing and decoding them here? I have a public key to use for the keys but I'm not sure how I can successfully dump unsigned and/or decoded tokens into the view so that I can see the scopes on my page for debugging.

Laravel API Registration

I am developing a laravel back-end to work with Vue JS, and trying to implement user registration. I have downloaded passport to authenticate users, but I am having trouble understanding exactly how a new user registers for the website.
From the research I have done it seems like you would want to make a route for the registration that can be accessed without using laravel passport, and then once the user is created grant it a token.
Once the user is registered Would I use a personal grant token, Implicit, or use passports CreateFreshApiToken middleware.
Should I put all my routes in web or the api route file?
You may create a route in your api where anybody can register a new user , so you do not use any auth middlewares, like for example:
Route::post('users', 'AuthController#register'); // Signup
and in your controller the related method:
/**
* API Register
*
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function register(Request $request)
{
$rules = [
'name' => 'unique:users|required',
'email' => 'unique:users|required',
'password' => 'required',
];
$input = $request->only('name', 'email','password');
$validator = Validator::make($input, $rules);
if ($validator->fails()) {
return response()->json(['success' => false, 'error' => $validator->messages()]);
}
$name = $request->name;
$email = $request->email;
$password = $request->password;
$user = User::create(['name' => $name, 'email' => $email, 'password' => Hash::make($password)]);
}
from this point the type of passport OAuth2 authorization code flow you choose will influence your implementation on the consumer app.
You may go for the classical OAuth2 flow where basically you have this steps:
Register the consumer application to the OAuth2 server and obtain the Client Id and Secret
The consumer application request an authorization code to the OAuth2 server using the Client Id and Secret
Once obtained the authorization code the consumer application can now request an access token to the OAuth2 server
The consumer application can now have access to the Api using in every request to the server its access token that it is sent in the header request.
Obviously each step above is an HTTP request and how you do it depends on the technology you use in the consumer application.
For example in php, you can use Guzzle and send an access token request like this:
$http = new \GuzzleHttp\Client;
$response = $http->post('http://yourserver.com/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'redirect_uri' => 'http://example.com/callback',
'code' => $request->code,
],
]);
On the other side you may want to use a simpler way to retrieve access tokens, avoiding all the authorization code request etc. using Laravel passport Personal Access Tokens. In this way you can issue access tokens for the user simply doing:
$user = App\User::find(1);
// Creating a token without scopes...
$token = $user->createToken('Token Name')->accessToken;
I encourage you to take a look to the Passport Laravel documentation and check all the possibilities offered.

Get authenticated user with Laravel Passport and grant password

I did an API REST with Laravel and now I'm trying to consume it. The thing is I need to authenticate users in the API and I am using the Password Grant method. I can authenticate users correctly and I can get an access token but from then, I don't see a way to retrieve the authenticated user with the access token in my consuming application.
I tried in the API with a route like this:
Route::get('/user', function(Request $request) {
$user = $request->user();
// Even with
$user = Auth::user();
return $user;
});
No dice. I am reading Passport code but I can't figure it out. My guess is that I would need to specify a new guard type or something because It doesn't seem that Laravel Passport provides one for this kind of grant type...
To clarify things:
I have an API REST application, which is the oAuth2 Server.
I have another application consuming the API REST.
I do know the workflow. In my case, with Password Grant, I get the user credentials in my consumer application, then I make a request to /oauth/token specifying the grant_type to password, I provide the user credentials along with my client credentials, which I am sure they were generated with "php artisan passport:client --password" (note the --password option)
I can get the access token with no problems. What I need now, is to get a JSON representation of the user I just authenticated from the API REST. But here is the problem: I just have an access token. Nothing I can relate with the user.
Or can I? Maybe I can extend the method that authenticates password grant requests to relate the generated access token to the user it is authenticating... *light bulb turns on*
Consuming application test code:
try {
$client = new Client();
$result = $client->post('https://myapi.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '5',
'client_secret' => 'my_secret',
'username' => 'user_I_am_authenticating',
'password' => 'the_user_password',
'scope' => '',
]
]);
$access_token = json_decode((string) $result->getBody(), true)['access_token'];
$result = $client->get('https://myapi.com/client/user', [
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => "Bearer $access_token",
]
]);
return (string) $result->getBody();
} catch (GuzzleException $e) {
return "Exception!: " . $e->getMessage();
}
Note that https://myapi.com/client/user route is just a route I made for testing in the API. That route is defined as:
Route::get('/user', function(Request $request) {
return $request->user();
});
Now. I know this is not working. This is what I want to achieve. Know the user making the request given the access_token/bearer_token.
You forgot the appropriate middleware.
Route::get('/user', function(Request $request) {
return Auth::user();
})->middleware('auth:api');
The authentication flow is not fired when you don't mention the auth middleware. That's why you get null.
I had the same problem with you. And i solved it after I manually defined the auth guard.
Route::get('/user', function (Request $request) {
return auth()->guard('api')->user();
});
You need to pass the Access token back with every request. Please check the documentation for this part here

How to limit user actions with Laravel Passport Scopes + Password Grant Type

I have set up the Laravel Passport package for Laravel 5.3 just as described in the official documentation (https://laravel.com/docs/5.3/passport#introduction).
I want the API to be consumed by a mobile application, so I am trying to implement Password Grant Tokens. I have created a password grant client, and the token request process...
$response = $http->post('http://my-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'my#email.com',
'password' => 'my-password',
'scope' => '',
],
]);
...Just works as expected, returning an access-token and a refresh-token for one of my users.
But now I want to define some scopes so I can limit the access of users... Following the documentation again, I have them defined in boot method of AuthServiceProvider.php like:
Passport::tokensCan([
'admin' => 'Perform every action',
'user' => 'Perform only normal user actions',
]);
In this scenario, if a "malicious" normal user requested a token (using the above POST call) specifying 'scope' => 'admin', he or she would get an 'admin' token... and that is not what I want.
Thus, I would like to know how is the workflow in this situation to effectively limit the access to normal users, and where do I have to implement the scope validation logic.
Thanks in advance.
One way to go about this would be to create a middleware
For example if you only want users with an email from example.com to request the admin domain you can do something like this
Example ScopeLogic.php middleware:
if ($request->input('grant_type') === 'password') {
$scope = $request->input('scope');
$username = $request->input('username');
if ($scope === 'admin' && (strpos($username, '#example.com') === false)) {
return response()->json(['message' => "Not authorized to request admin scope"], 401);
}
}
return $next($request);
Of course, you would have to add this scope to your $routeMiddleware array in Kernel.php
protected $routeMiddleware = [
...
'check-scopes' => \App\Http\Middleware\ScopeLogic::class
]
As well as wrap Passport::routes() in AuthServiceProvider.php to check for this middleware
\Route::group(['middleware' => 'check-scopes'], function() {
Passport::routes();
});
Passport will also check that a correct username and passport combination was passed so you don't have to worry about that in the middleware
In my opinion, I think what confuses most people with OAuth and APIs is that scopes are tied to "clients" and not the "resource owner" themselves. Clients should be able to talk to an API using an admin scope or no scopes at all if needed. If they use an admin-ish type scope together with user context (password grant, authorization code grant, etc), then there is no stopping them from making calls that require such a scope against that user in the API. To me, the only person that can truly be classified as malicious would be one who manages to steal an access token containing an admin scope. That is why API implementors are allowed to specify what scopes to grant a client and if it's a first party app that uses something like the Password Grant, then you as a user has no choice but to trust it with your data.
I don't know how one would do this and use the retrieved token inside another's mobile app but if you did try requesting a token manually yourself with an admin scope, then I really don't see anything wrong that (other than you giving the app more control with you set as user context, so it may even be counter productive?)
If you need more control than that, then you need to go past your API and create something like application-level permissions for each user inside your resource server.
I forget where I read it, some Github issues somewhere, but apparently Laravel doesn't have that ability built in, each client is the treated the same equally, out of the box.
A user provided a good solution, and I built upon it here: https://stackoverflow.com/a/55285483/1132557

Globally accessible user object in controllers and models

I'm building a Laravel API which authenticates users using an authentication token. For any routes that need authentication, I'm wrapping them in an auth filter:
Route::group(array('before' => 'auth'), function() {
Route::get('user/account', 'UserController#getAccountDetails');
});
My auth filter basically decrypts the passed in authentication token and checks if it's valid:
Route::filter('auth', function()
{
// Try catch because Crypt::decrypt throws an exception if it's not a valid string to decrypt
try {
$authToken = Crypt::decrypt(Request::header('Authorization'));
// If there's a user tied to this auth token, it's valid
$user = AuthToken::where('token', '=', $authToken)->first()->user()->first();
if (!$user) {
throw new \Exception();
}
// Make the user globally accessible in controllers
} catch (\Exception $e) {
return Response::json([
'data' => [
'error' => 'You must be logged in to access this resource.'
],
'success' => false,
'status' => 403
], 403);
}
});
Pretty simple stuff, but I'm stuck on the next part. I want to be able to easily retrieve the current user record in my controllers and models.
For example, if I used Laravel's Auth library I could get the current user by doing Auth::user() in my controllers. I'd like to have that kind of functionality but I'm not sure how to build it. Could I write a class that gets instantiated after authentication with a static method that returns a User model?
Not sure if that's an option for you, but maybe you would like to use oauth2 instead of writing "your own" token based authentication?
There is quite nice ouath2 server wrapper for laravel project: oauth2-server-laravel.
According to it's documentation you can (for example for password flow authentication) put this in it's config:
'password' => array(
'class' => 'League\OAuth2\Server\Grant\Password',
'access_token_ttl' => 604800,
'callback' => function($username, $password){
$credentials = array(
'email' => $username,
'password' => $password,
);
$valid = Auth::validate($credentials);
if (!$valid) {
return false;
}
return Auth::getProvider()->retrieveByCredentials($credentials)->id;
}
)
And than you can you can authenticate (via username and password in that case) sending post request like that:
POST https://www.example.com/oauth/access_token?
grant_type=password&
client_id=the_client_id&
client_secret=the_client_secret&
username=the_username&
password=the_password&
scope=scope1,scope2&
state=123456789
Request will return generated token, and then you can make api calls as usual, just putting the token in the post data.
In your api logic getting the user by token is quite simple in that case, just run:
User::find(ResourceServer::getOwnerId());
It will makes stuff like: refresh tokens, other grant flows, scope access, clients management a lot easier. Out of the box in fact.
You can also secure any particular route like that:
Route::get('secure-route', array('before' => 'oauth', function(){
return "oauth secured route";
}));
You can find more details in oauth2-server-laravel documentation: https://github.com/lucadegasperi/oauth2-server-laravel
And oauth2 documentation: http://oauth.net/documentation/
It is true that the Auth::user() method is quite convenient. So, why not simply extend the Auth class to write your own authentication driver ? You can find all needed doc here.
You can then just use the Auth facade just like in every other laravel app you could write… wonderful, isn't it ?

Categories