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 ?
Related
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
I'm trying to implement token-based authentication in Laravel 5.5 using Tymon's JWTAuth. I followed the GitHub Documentation for the library and am using the following authentication flow. Here is the authentication portion of my login route:
try {
// attempt to verify the credentials and create a token for the user
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['success' => false, 'error' => 'Invalid Credentials. Please make sure you entered the right information and you have verified your email address.'], 401);
}
}
catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['success' => false, 'error' => 'could_not_create_token'], 500);
}
// all good so return the token
return response()->json(['success' => true, 'data'=> [ 'token' => $token ]]);
And here are the routes:
Route::group([
'middleware' => ['jwt.auth', 'jwt.refresh'],
],
function () {
// Routes requiring authentication
Route::get('/logout', 'Auth\LoginController#logout');
Route::get('/protected', function() {
return 'This is a protected page. You must be logged in to see it.';
});
});
So you can see I am using the jwt.auth and jwt.refresh middlewares. Now, everything is seemingly working as expected and I can authenticate users with tokens. Each token has a lifespan of one use and I am provided another valid token after each request (the refresh flow).
However, my problem is that if I have a valid token for a user that has not been used yet, and then I remove it from the header and hit the /login route with valid credentials, I am issued another valid token. So now I have two valid tokens that can be used to authenticate a user, as my /login route is not invalidating previously issued tokens.
Does anyone know of a way to check to see if a user has an outstanding valid token, so that it can be invalidated if the user logs in from elsewhere?
I'll answer my own question after doing some research. From my understanding, JWT tokens are valid unless explicitly blacklisted. As long as the server recognizes that it itself created the token, then it can decipher the token using a secret key and assume that it's valid. That's why token lifetimes are so important. So if you want to invalidate an issued token, you'd either have to wait for expiry or blacklist it.
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
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
After the registration was successful I wanted to pass all the data to a login route as POST. How do you do this in laravel 4?
I know I can just authenticate the user after registration but the login page has other parameters and more required authentication process.
So I wish to push the username and password entered in the registration process into the login process so it can go through with the other processes inside the login route. (ex. Token generation which requires post data of app_id and app_secret)
You can use Laravel Events do to that:
Register your event in filters.php or create an events.php and add it to composer.json:
Event::listen('user.registered', function($data)
{
var_dump($data['app_id']);
var_dump($data['app_secret']);
var_dump($data['user']);
});
And fire it in your register() method:
public function register()
{
/// register your user:
$user = new User;
...
$user->save();
/// fire the event:
Event::fire('user.registered', array(
'app_id' => 'API',
'app_secret' => 'WHATEVER',
'user' => $user
));
/// Redirect the user to where it should go next:
return Redirect::to('/');
}
Why not move alot of that extra logic into the User model. Then you can call $user->crazySecureLogin(); after logging in and after registering. That way you stay DRY, and it could possibly clean up your login controller method too. And to top it off, you don't have to worry about sending a post request as a response.
return Redirect::to('routename')->withInput();
or
return Redirect::to('routename')->with('email', $emailvalue)->with('password', $passwordValue)
etc
add namespace on top of your class
use Redirect;
add this code to your redirect stage.
return Redirect::route('route_name',['username' => $request->username,
'password' => $request->password]);