I want to get current route group.
For example group1 from
$app->group('/group1', function () use ($app) {
$app->get("/a"...
$app->get("/b"...
$app->get("/c"...
...
I can get route pattern from current route. The question is - is there any special way to get current route group?
What about $this ? The code is taken from their website.
$app = new \Slim\App();
$app->group('/users/{id:[0-9]+}', function () {
$this->map(['GET', 'DELETE', 'PATCH', 'PUT'], '', function ($request, $response, $args) {
// Find, delete, patch or replace user identified by $args['id']
})->setName('user');
$this->get('/reset-password', function ($request, $response, $args) {
// Reset the password for user identified by $args['id']
})->setName('user-password-reset');
});
Related
I'm trying to implement authentication & authorization of users between my microservices and API Gateway.What I have now:
API Gateway which can request to any microservice.
User microservice - where I'm storing all users. laravel/passport implemented to authenticate user in this microservice. Works as it should be, login route returns token which I'm using to authenticate user in this microservice.
Other 5 microservices without any authentication or authorization.
Question is: what is the right way to use authentication & authorization with microservices? I know that I should authenticate users in my API Gateway and authorization will happen inside microservices. But how authorization in other microservices happening if they don't know anything about users?
I'm planning to use somehow JWT token with information about user roles but haven't found yet how to put that information into token
I'll try to explain with a basic example for API.
Let's say you have currently 3 microservices :
Users
Posts
Core
I assume you're using httpOnly cookie to store user token.
In Core microservice I have this route structure:
Route::prefix('core')->group(function () {
Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
Route::middleware('scope.trader')->group(function () {
Route::get('user', [AuthController::class, 'user']);
});
});
Now i want to login which i should send an API request, and I should think of a solution to send token anytime I need it.
login(this is where you get token) and register don't need token
user need token (this is where you asked for solution)
So in addition to get a result, I should create a service for user, and here how I've done it :
UserService :
class UserService extends ApiService
{
public function __construct()
{
// Get User Endpoint Microservice API URL
$this->endpoint = env('USERS_MS') . '/api';
}
}
ApiService :
abstract class ApiService
{
protected string $endpoint;
public function request($method, $path, $data = [])
{
$response = $this->getRequest($method, $path, $data);
if ($response->ok()) {return $response->json();};
throw new HttpException($response->status(), $response->body());
}
public function getRequest($method, $path, $data = [])
{
return \Http::acceptJson()->withHeaders([
'Authorization' => 'Bearer ' . request()->cookie('token')
])->$method("{$this->endpoint}/{$path}", $data);
}
public function post($path, $data)
{
return $this->request('post', $path, $data);
}
public function get($path)
{
return $this->request('get', $path);
}
public function put($path, $data)
{
return $this->request('put', $path, $data);
}
public function delete($path)
{
return $this->request('delete', $path);
}
}
If you're wondering where, this UserService come from, then I should say, I've created a package to use it in other microservices, so you can do the same or just create a service and use it in your microservices or etc.
Everything is obvious about ApiService, but I'll try to explain the base.
Anytime we want to do an API call, we can simply call Allowed methods in this class, then our methods, will call request, to pass common arguments, and eventually using those arguments to do the API call.
getRequest method, is doing the call and get the stored token from httpOnly cookie, and will send it as an Authorization header to the target endpoint, and eventually it'll return whatever it get from target.
So If we want to use this, we can simply do like this in our controller :
class AuthController extends Controller
{
// use Services\UserService;
public UserService $userService;
/**
* #param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function register(RegisterRequest $request)
{
$data = $request->only('name', 'email', 'password') + ['additional_fileds' => 0 ];
// additional fields can be used for something except from request and
// optional, like is it admin or user or etc.
// call the post method, pass the endpoint url(`register`), pass $data
$user = $this->userService->post('register', $data);
// get data from target endpoint
// and ...
return response($user, Response::HTTP_CREATED);
}
public function login(Request $request)
{
// same thing here again, but this time i passed scope to help me
// get the specific user scope
$data = $request->only('email', 'password') + ['scope' => 'writer'];
$response = $this->userService->post('login', $data);
// as you can see when user do success login, we will get token,
// which i got that token using Passport and set it to $cookie
$cookie = cookie('token', $response['token'], 60 * 24); // 1 day
// then will set a new httpOnly token on response.
return response([
'message' => 'success'
])->withCookie($cookie);
}
public function user(Request $request)
{
// Here, base on userService as you saw, we passed token in all requests
// which if token exist, we get the result, since we're expecting
// token to send back the user informations.
$user = $this->userService->get('user');
// get posts belong to authenticated user
$posts = Post::where('user_id', $user['id'])->get();
$user['posts'] = $posts;
return $user;
}
}
Now, how about user microservice? well Everything is clear here, and it should work like a basic app.
Here's the routes :
Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
Route::middleware(['bunch','of', 'middlewares'])->group( function (){
Route::get('user', [AuthController::class, 'user']);
});
And in controller :
class AuthController extends Controller
{
public function register(Request $request)
{
$user = User::create(
$request->only('first_name', 'email', 'additional_field')
+ ['password' => \Hash::make($request->input('password'))]
);
return response($user, Response::HTTP_CREATED);
}
public function login(Request $request)
{
if (!\Auth::attempt($request->only('email', 'password'))) {
return response([
'error' => 'user or pass is wrong or whatever.'
], Response::HTTP_UNAUTHORIZED);
}
$user = \Auth::user();
$jwt = $user->createToken('token', [$request->input('here you can pass the required scope like trader as i expalined in top')])->plainTextToken;
return compact('token');
}
public function user(Request $request)
{
return $request->user();
}
}
So here's the complete example and you can use the Core microservice approach on other microservices to get your information related to authenticated user, and as you can see everything will be authenticated due to those requests from core to other microservices.
I'm building a rest api with slim 3 but I'm having some trouble understanding how to do the routing. I initially had the get method working correctly at api.com/v1/companies/get and api.com/v1/companies/get/id and post method at api.com/v1/companies/post, but I refactored so all the methods would be at api.com/v1/companies/id and after this refactoring I get a 405 error on post requests saying that only the get method exists.
So I did a little more researching; the amount of small, but breaking inconsistencies I've found in other slim 3 guides has been a bit annoying, but it looks like my solution is the map() function, only I have no idea how to use it, and even the official docs skip over the part I don't understand.
This is how the code looked after the refactor that broke it:
$app->group('/v1', function() use ($app) {
$app->group('/companies', function() use ($app) {
$app->get('/{code}', function($request, $response, $args) {...}
$app->get('', function($request, $response, $args) {...}
$app->post('', function($request, $response, $args) {...}
});
});
And my first attempts at using map():
$app->group('/v1', function() use ($app) {
$app->map(['GET', 'POST', 'PUT', 'DELETE'], '/companies/{code}', function($request, $response, $args) use ($app) {
//here's where I don't know what to do
if($request->isGet()) {
//What goes here? And I seem to be having problems accessing my parameters?
}
}
}
This code works for me:
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../vendor/autoload.php';
$app = new \Slim\App;
$app->group('/v1', function() {
$this->map(['GET', 'POST', 'PUT', 'DELETE'], '/companies/{code}', function($request, $response, $args) {
if($request->isGet()) {
$response->getBody()->write("it's GET");
}
if($request->isPost()) {
$response->getBody()->write("it's POST");
}
if($request->isPut()) {
$response->getBody()->write("it's PUT");
}
if($request->isDelete()) {
$response->getBody()->write("it's DELETE");
}
return $response;
});
});
$app->run();
Please don't use $app inside group. In docs you can see that $this inside group point to instance of 'Slim\App' already. Also check your .htaccess file if it's configured as described in Slim3 documentation.
How can I get route pattern inside middleware:
routes.php:
$app->get('/myroute/{id}', function($req, $res, $args) {
//DO STUFF HERE
})->add(new MyMiddle());
middle.php:
class MyMiddle {
public function __invoke($req, $res, $next) {
//DO STUFF
}
}
In routes.php I can get {id} with $args['id'], but how can I get it inside MyMiddle.php?
Thank you,
Cristian Molina
Enable the determineRouteBeforeAppMiddleware setting:
$config = ['settings' => [
'determineRouteBeforeAppMiddleware' => true,
'displayErrorDetails' => true,
]];
$app = new \Slim\App($config);
You can now access the Route object from the Request, using getAttribute() and, from the route, get at the arguments:
$app->add(function ($request, $response, $next) {
$id = $request->getAttribute('route')->getArgument('id');
return $next($request, $response);
});
I decided to included a Slim v2 example as that is what I was looking for when I came across this post. You can use $this->app->router()->getCurrentRoute()->getPattern() from the slim.before.dispatch callback hook to accomplish the same thing.
How do I use slim framework route get all .. but not include except string get /login
$app->get('/.*?', function () use ($uri, $app) {
$app->redirect($uri['public'].'/login');
});
$app->get('/login', function () use ($uri, $app) {
echo 'login view';
});
...
$app->post('/login', function () use ($uri, $app) {
$user_controller = new controller\user_controller();
$user_controller_login = $user_controller->login($uri, $app);
});
Slim routes are processed in order, so if you define the /login route before the catch-all, it will work in that order:
$app->get('/login', function() use($app) { ... });
$app->post('/login', ...);
$app->get('/.*', function() use($app) { $app->redirect('/login'); });
Although, I don't usually see 'catch all' style routes. Usually, you'd use URL rewriting to pass to static files not served by routes, and if you're doing this to ensure the user has logged-in to each page, you are better off using Slim Middleware to handle that.
For instance, if you had an authenticate piece of middleware, it could check on each of your routes for a login session/cookie/whatever, and if not found redirect to the login page (also passing the current url so they could be redirected back after login).
$authenticate = function() {
$app = \Slim\Slim::getInstance();
return function() use($app) {
// check for auth here somehow
if (!isset($_SESSION['user'])) {
$app->redirect('/login');
}
};
}
// Use middleware on your defined routes
$app->get('/stuff', $authenticate, function() use($app) { ... });
...
I need to match some different routes to aim the same page.
E.g.: / , /inicio, /home -> must show the landing page
$app->get('/', function () use ($app) {
return $app['twig']->render(
'home.html.twig',
array(
'page' => 'home'
)
);
})
->bind('home');
And I'd like to avoid the same for each route. I'd like to do something like
$app->match('/|/home|/inicio, function() use ($app) {} );
Maybe you're looking for this:
$app->get("/{slug}", function ($slug) use ($app) {
// your regular action logic
})->assert("slug", '^(home|inicio)$');
Here's the link to the docs on route requirements.