I am using Oauth-server-laravel Authentication.
What i have done so far:
When i post wrong access_token to API that i have created in laravel it gives following response,
{
"error": "access_denied",
"error_description": "The resource owner or authorization server denied the request."
}
I have used oauth as middleware in route as follow,
Route::group(['namespace' => 'Modules\User\Http\Controllers', 'middleware' => 'oauth'], function () {
// Get User Profile Details
Route::post('getUserProfileDetail', 'UserController#getUserProfileDetail');
});
Problem:
If credentials are wrong then oauth automatically respond with it's default message and i want to customize that response messages,
I have half succeed in that if credentials are correct then it calls function that specifies in route and in that i am appending mre response that i want to send.
$response = $this->authorizer->issueAccessToken();
$code = 200; //Response OK
$response['result'] = 'success';
$response['user_id'] = $user['id'];
$response['email'] = $user['email'];
$response['name'] = $user['name'];
But i'm unable to send it when credentials are not correct because it can't call function and send it's default response to user.
I fall this types of problem to set custom message then this worked for me (a blog post I wrote about this article)]
So first create a middleware in app/Http/Middleware.My middleware name is OauthExceptionMiddleware
Then open
app/Http/kernel.php
and put this middleware instead of oauth2 previous middleware in $middleware array,like this
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\OauthExceptionMiddleware::class,
];
Oauth2 Custom Exception Error Message
<?php
/**
* Created by PhpStorm.
* User: kingpabel
* Date: 3/23/16
* Time: 4:40 PM
*/
namespace app\Http\Middleware;
use Closure;
use League\OAuth2\Server\Exception\OAuthException;
class OauthExceptionMiddleware
{
public function handle($request, Closure $next)
{
try {
$response = $next($request);
// Was an exception thrown? If so and available catch in our middleware
if (isset($response->exception) && $response->exception) {
throw $response->exception;
}
return $response;
} catch (OAuthException $e) {
$data = [
// 'error' => $e->errorType,
// 'error_description' => $e->getMessage(),
'error' => 'Custom Error',
'error_description' => 'Custom Description',
];
return \Response::json($data, $e->httpStatusCode, $e->getHttpHeaders());
}
}
}
Related
I'm currently working on a Laravel 8 project, I have two projects:
A Laravel 8 project used as an API, it exposes some endpoints
A Laravel Lumen 8 project which runs on it's own domain.
Both have Cors enabled, and both run on the same domain, I'm having issues with Guzzle in my Lumen project connecting to an endpoint that exists on my Laravel API, here's the scenario and request flow:
A request comes in to: /hub/microservice/fudge-api-reports on the Laravel api, this is it's controller method:
/**
* Route the microservice
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function microservice(Request $request, $service)
{
$validator = Validator::make($request->all(), [
'endpoint' => 'required|string',
'method' => 'required|string|in:post,get',
'data' => 'nullable|array'
]);
if ($validator->fails()) {
return response()->json([
'message' => "One or more fields has been missed or is invalid.",
'errors' => $validator->messages()
], 400);
}
$microservice = Microservice::where('microservice', $service)->first();
if (!$microservice) {
return response()->json([
'message' => "The microservice you're trying to access is invalid or doesn't exist"
], 404);
}
// auth
$auth = 'Bearer ' . $microservice->hub_access_token;
// the url
$url = $microservice->hub_domain . $request->input('endpoint');
// define how to communicate with the microservice
if ($request->input('method') == 'post') {
if ($microservice->hub_access_token) {
$response = Http::timeout(60)->withHeaders([
'Authorization' => $auth
])->post($url, $request->input('data'));
} else {
$response = Http::timeout(60)->post($url, $request->input('data'));
}
} else {
if ($microservice->hub_access_token) {
$response = Http::timeout(60)->withHeaders([
'Authorization' => $auth
])->get($url);
} else {
$response = Http::timeout(60)->get($url);
}
}
// the response from the microservice
return response()->json($response->json(), $response->status());
}
The request then (based on the endpoint and method) goes to the microservice which runs on it's own domain, mine is: http://localhost:8001/, my URL would be a GET request, and would have an access token, so it makes it into the else statement above and into the first if, e.g: http://localhost:8001/api/reports?report=MyReport and has a token as the header.
The request comes into the fudge-api-reports Laravel Lumen project, and goes to a controller method, but first passes through my BeforeMiddleware where it performs a "log in" request back to the Laravel API to authenticate and check the abilities, this part of the middleware is:
<?php
namespace App\Http\Middleware;
use Closure;
use GuzzleHttp\Client;
class BeforeMiddleware
{
/**
* Request attributes
*
*/
public $attributes;
/**
* Get API url
*/
protected function getApiUrl()
{
return rtrim(config('fudge.fudge_api_domain'), '/');
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $ability)
{
$api = $this->getApiUrl();
$token = $request->input('token');
if (!$token && $request->header('Authorization')) {
$token = explode(' ', $request->header('Authorization'))[1];
}
// TODO: this part appears to ALWAYS time out, despite
// http://localhost:8000/api/hub/login working just fine via Postman
$client = new Client([
'base_uri' => $api,
'timeout' => 5
]);
$res = $client->request('POST', '/api/hub/login', [
'token' => $token,
'ability' => "reports:$ability"
]);
// the response
$res = $res->json();
$hasAbility = isset($res['has_ability']) && !empty($res['has_ability']) ? $res['has_ability'] : false;
// not authorised
if (!$hasAbility) {
return response()->json([
'message' => "You aren't authorised"
], 200);
}
// add the hub's user to the request
$request->attributes->add(['has_ability' => $hasAbility]);
// Post-Middleware Action
return $next($request);
}
}
If the Hub log in is successful, then a has_ability is returned with the value of true back to the the middleware, which then goes through the controller method and finally returns the response back to the initial request of: /hub/microservice/fudge-api-reports
My issue
in my BeforeMiddleware my Guzzle POST request always fails, and never returns a response, it always seems to time out accessing my /api/hub/login endpoint that exists in my Laravel project.
It works perfectly fine through Postman, and Cors is enabled, why would this always timeout in the context of the Middleware, what am I missing?
I'm following this tutorial https://medium.com/tech-tajawal/jwt-authentication-for-lumen-5-6-2376fd38d454 to learn how to setup a tokenbased authentication for my lumen backend.
So far, everything worked fine. Only in the end it turns out that I cant get a token Oo
I don't think that theres anything wrong with my code.
When using RESTClient to do an http-Request to: "http://localhost:8080/users"
I get the desired "'error' : 'Token not provided'"
However, when I try to get the token as described in the tutorial, I get errors that the name and e-mail field aren't provided.
Here is my controller code (or the code from the tutorial) which evaluates the request for the transmitted name and email values:
<?php
namespace App\Http\Controllers;
use Validator;
use App\User;
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Firebase\JWT\ExpiredException;
use Illuminate\Support\Facades\Hash;
use Laravel\Lumen\Routing\Controller as BaseController;
class AuthController extends BaseController
{
/**
* The request instance.
*
* #var \Illuminate\Http\Request
*/
private $request;
/**
* Create a new controller instance.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
public function __construct(Request $request) {
$this->request = $request;
}
/**
* Create a new token.
*
* #param \App\User $user
* #return string
*/
protected function jwt(User $user) {
$payload = [
'iss' => "lumen-jwt", // Issuer of the token
'sub' => $user->id, // Subject of the token
'iat' => time(), // Time when JWT was issued.
'exp' => time() + 60*60 // Expiration time
];
// As you can see we are passing `JWT_SECRET` as the second parameter that will
// be used to decode the token in the future.
return JWT::encode($payload, env('JWT_SECRET'));
}
/**
* Authenticate a user and return the token if the provided credentials are correct.
*
* #param \App\User $user
* #return mixed
*/
public function authenticate(User $user) {
$this->validate($this->request, [
'email' => 'required|email',
'password' => 'required'
]);
// Find the user by email
$user = User::where('email', $this->request->input('email'))->first();
if (!$user) {
// You wil probably have some sort of helpers or whatever
// to make sure that you have the same response format for
// differents kind of responses. But let's return the
// below respose for now.
return response()->json([
'error' => 'Email does not exist.'
], 400);
}
// Verify the password and generate the token
if (Hash::check($this->request->input('password'), $user->password)) {
return response()->json([
'token' => $this->jwt($user)
], 200);
}
// Bad Request response
return response()->json([
'error' => 'Email or password is wrong.'
], 400);
}
}
And here is the code of the middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use Exception;
use App\User;
use Firebase\JWT\JWT;
use Firebase\JWT\ExpiredException;
class JwtMiddleware
{
public function handle($request, Closure $next, $guard = null)
{
$token = $request->get('token');
if(!$token) {
// Unauthorized response if token not there
return response()->json([
'error' => 'Token not provided.'
], 401);
}
try {
$credentials = JWT::decode($token, env('JWT_SECRET'), ['HS256']);
} catch(ExpiredException $e) {
return response()->json([
'error' => 'Provided token is expired.'
], 400);
} catch(Exception $e) {
return response()->json([
'error' => 'An error while decoding token.'
], 400);
}
$user = User::find($credentials->sub);
// Now let's put the user in the request class so that you can grab it from there
$request->auth = $user;
return $next($request);
}
}
Which I have registered in bootstrap/app.php:
$app->routeMiddleware([
'jwt.auth' => App\Http\Middleware\JwTMiddleware::class,
]);
But my JSON Payload looks like this so it SHOULD work.
[
{
"email": "ibeier#hotmail.com,
"name": "Eusebio Dach",
}
]
However I must admit that the tutorial in this section was a bit lacking in the details.
Here is that (last) part which I cant get to work:
"
To make this privious request successful we need to provide the token as query parameter. We can get the token by hitting the following route http://localhost:8000/auth/login inside Postman application. Now that you have the token open the priviously failed request and this time in the Params section create a key token and set it’s value to the token that you received in previous request. And after that send the request to http://localhost/users route and you will get the users list in response.
(Screenshot with userdata, see tutorial)
And that about wraps it up. If you have any questions feel free to leave your comments below.
"
I'm not sure whether I provided the correct request data and if I have set all headers correctly.
Maybe someone with a bit more knowledge in this can help me understanding what I am missing.
I already tried looking into other tutorials but these take different jwt extensions for their lumen and also seem to take other approaches in their controller code etc..
I'd very much like to follow this tutorial since its approach seemed pretty straight-forward to me.
It would also help if someone could tell me that the tutorial worked for them, and maybe also tell me what they did in addition to the instructions provided through the tutorial.
Because then I would just do the tutorial again and see if I can get it working.
Now, I'm using api.php route for requests from Axios on VueJS , And I need to logout from Auth::guard('web')->logout(); command but, at the moment, I cannot do this.
routes/api.php
Route::group([ 'prefix' => 'v1/auth', 'middleware' => 'jwt'], function () { //
Route::get('me', 'Auth\UserController#me');
Route::get('gg', 'Auth\UserController#test');
});
app/Http/sMiddleware/JwtMiddleware.php
<?php
namespace App\Http\Middleware;
use Closure;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Tymon\JWTAuth\Exceptions\JWTException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Facades\JWTAuth;
use Illuminate\Support\Facades\Auth;
class RefreshToken extends BaseMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try
{
if (! $user = JWTAuth::toUser(JWTAuth::getToken()))
{
return response()->json([
'code' => 101, // means auth error in the api,
'response' => 'not authenticate' // nothing to show
]);
}
}
catch (TokenExpiredException $e)
{
// If the token is expired, then it will be refreshed and added to the headers
try
{
$refreshed = JWTAuth::refresh(JWTAuth::getToken());
header('Authorization: Bearer ' . $refreshed);
}
catch (JWTException $e)
{
return response()->json([
'code' => 103, // means not refreshable
'response' => 'token jwt exception' // nothing to show
]);
}
}
catch (JWTException $e)
{
Auth::guard('web')->logout(); // here
return response()->json([
'code' => 101, // means auth error in the api,
'response' => 'jwterror' // nothing to show
]);
}
return $next($request);
}
}
But when i migrated from api.php to web.php. I can use Axios to post for logout
Please, tell me how to use Auth::logout in api route file.
Sorry I'm not good at english.
Logout is implemented with the session driver, and unlike there web guard, the api guard is using a token driver not session driver.
Basically, the user is not logged into the API, but WEB part of your application.
In the api; find a way to invalidate/expire the token so that the user with that token can no longer access the api resources.
try {
JWTAuth::invalidate($request->input('token'));
return response()->json(['success' => true, 'message'=> "You have successfully logged out."]);
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['success' => false, 'error' => 'Failed to logout, please try again.'], 500);
}
Web logout
Session Logout
I'm using ZendFramework 3 in my REST API project. So there are few modules and a plugin which checks an authorization status. If the authorization fails it throws an Exception.
There is no way to handle it in each controller separately using try .. catch. How can I intercept and handle the Exception and generate JSON output like this?
{
message: "Access denied",
reason: "Your token is incorrect"
}
I'm a newbie in ZendFramework, that's why I have no idea how to do this. And official documentation didn't say a word about this.
There are default framework Events that are triggered including the event MvcEvent::EVENT_DISPATCH_ERROR. So, all you should do is to attach listener on that error event and return JSON response.
First, you need to register your Listener in module.config.php
// In my case module name is Api
'listeners' => [
Api\Listener\ApiListener::class // Register the class listener
],
'service_manager' => [
'invokables' => [
// Register the class (of course you can use Factory)
Api\Listener\ApiListener::class => Api\Listener\ApiListener::class
],
],
Second, create the file class Api/Listener/ApiListener.php
<?php
namespace Api\Listener;
use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventManagerInterface;
use Zend\Mvc\MvcEvent;
use Zend\Console\Request as ConsoleRequest;
use Zend\View\Model\JsonModel;
class ApiListener extends AbstractListenerAggregate
{
public function attach(EventManagerInterface $events, $priority = 1)
{
// Registr the method which will be triggered on error
$this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR,
[$this, 'handleError'], 0);
}
/**
* Return JSON error on API URI(s)
*/
public function handleError(MvcEvent $e)
{
$request = $e->getParam('application')->getRequest();
if($request instanceof ConsoleRequest){
return;
}
//If you want to convert Response only on some URIs
//$uri = $request->getUri()->getPath();
//if(0 !== strpos($uri, '/api')){
// return;
//}
$response = $e->getResponse();
$exception = $e->getResult()->exception;
$errorType = $e->getError();
$errorCode = $exception && $exception->getCode() ? $exception->getCode() : 500;
$errorMsg = $exception ? $exception->getMessage() : $errorType;
$json = new JsonModel(['message' => $errorMsg]);
$json->setTerminal(true);
$response->setStatusCode($errorCode);
$e->setResult($json);
$e->setViewModel($json);
}
}
That's all. Now on every error, your custom logics will be executed.
Trying to get the header authorization key in controller for making an API. Request is making from fiddler.
$headers = apache_request_headers();
And the $header contains an array.
Array
(
[User-Agent] => Fiddler
[Host] => localhost:8000
[Content-Length] => 102
[Authorization] => TestKey
)
If am trying like this to fetch the Authorization , its throwing error.
$header['Authorization]
Error :
Undefined index: Authorization
Tried many ways to get the authorization, but not working anything. Is there any way to fetch this?
To get headers from the request you should use the Request class
public function yourControllerFunction(\Illuminate\Http\Request $request)
{
$header = $request->header('Authorization');
// do some stuff
}
See https://laravel.com/api/5.5/Illuminate/Http/Request.html#method_header
Though it's an old topic, it might be useful for somebody...
In new Laravel versions, it's possible to get bearer Authorization token directly by calling Illuminate\Http\Request's bearerToken() method:
Auth::viaRequest('costom-token', function (Request $request) {
$token = $request->bearerToken();
// ...
});
Or directly from a controller:
public function index(Request $request) {
Log::info($request->bearerToken());
// ...
}
If you use a specific package like "JWT" or "sanctum" you can use their own middleware to retrieve user information.
Also, Laravel provides many ways to get the authorization key like :
$request->bearerToken(); to get only token without 'Bearer' word the result will be like 44|9rJp2TWvTTpWy535S1Rq2DF0AEmYbEotwydkYCZ3.
$request->header('Authorization'); to get the full key like Bearer 44|9rJp2TWvTTpWy535S1Rq2DF0AEmYbEotwydkYCZ3
P.S. you can use request() shortcut instead of using $request variable
You can try install the jwt(JSON Web Token Authentication for Laravel & Lumen) http://jwt-auth.com via composer.
And create a middleware that verify if exists yours token key in the header request.
After to install jwt, Your middleware it could be as follows
<?php
namespace App\Http\Middleware;
use Closure;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class VerifyJWTToken
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
$user = JWTAuth::toUser($request->header('token'));
} catch (JWTException $e) {
if ($e instanceof TokenExpiredException) {
return response()->json([
'error' => 'token_expired',
'code' => $e->getStatusCode()
], $e->getStatusCode());
}
else if($e instanceof TokenInvalidException){
return response()->json([
'error' => "token_invalid",
'code' => $e->getStatusCode()
], $e->getStatusCode());
}
else {
return response()->json([
'error' => 'Token is required',
'code' => $e->getStatusCode(),
], $e->getStatusCode());
}
}
return $next($request);
}
}
I used token for example for a header key, but you can name it, as you like.
Then you could use this on any controller
There is a simple way to get headers in any file or controller with $_SERVER.
print_r($_SERVER); // check your header here
then, you can simply get your header:
$AuthToken = $_SERVER['HTTP_AUTHORIZATION'];