Lumen 5.5 Session store not set on request - vue.js

I use vue-authenticate (https://github.com/dgrubelic/vue-authenticate) to create two kinds of connection on our web service, the first method is the connection to his account, the second method is the addition of account when connected.
I use Lumen (by Laravel) for backend and connection management in PHP.
Only sessions are not available under Lumen, how do I store temporary credentials?
use League\OAuth1\Client\Server\Twitter;
public function login(Request $request)
{
try {
$this->server = new Twitter([
'identifier' => $this->key,
'secret' => $this->secret,
'callback_uri' => $request->get('redirectUri'), // Variable getted from POST
]);
if(empty($request->get('oauth_token'))) {
$temporaryCredentials = $this->server->getTemporaryCredentials();
$request->session()->put('temporary_credentials', serialize($temporaryCredentials)); // Session doesn't works
return response()->json([
'oauth_token' => $temporaryCredentials->getIdentifier(),
'oauth_token_secret' => $temporaryCredentials->getSecret(),
], 200);
} else {
// I must have oauth_token here with session
}
} catch (\Exception $e) {
return response()->json($e->getMessage(), 500);
}
}

I think you just misunderstood the concept of Web Service (API). API is not a stateful application, rather it's a stateless, means no session available for each request. So, in major API framework, session is not supported (officially). To handle your problem, you can store your temporary credentials in a database or maybe in a cache (with TTL, eg: 60 minutes), like this:
$requestIdentifier = $request->getClientIdentifier(); // YOU SHOULD IMPLEMENT THIS METHOD
Cache::put($requestIdentifier, $temporaryCredentials, 60);
To retrieve your cache just use:
$temporaryCredentials = Cache::get($requestIdentifier);
Here I give you some idea, when you implement getClientIdentifier, you can force the client to send a unique key inside your header, like:
axios.post('http://somewhere', {
headers: {
'x-request-identifier': UNIQUE_ID
}
})
In your API:
$requestIdentifier = $request->header('x-request-identifier');

Related

API Authorization Tool

I'm working on an application with RESTful API endpoints that needs proper authorization security using an RBAC system. So far, I've looked into Keycloak. It looks promising at first but doesn't support granular authorization control of an endpoint, which is a hard requirement. For example, if I have the endpoint /object/<object:id>, a list of object IDs [1,2,3,4] and a test user, there's no way to restrict the test user to only have access to object IDs [1,2] but not [3,4] for the same endpoint. It seems the user will have access to all the IDs or none. Perhaps this can be accomplished by customizing or extending the base Keycloak server but there isn't enough documentation on the Keycloak website on how to do so.
I've done a search for other RBAC permissions systems but haven't been able to find much. Are there any authorization systems out there that can accomplish this?
but doesn't support granular authorization control of an endpoint
Check out Auth0's Fine Grained Authorization solution: https://docs.fga.dev/. (Disclaimer: I am employed by Auth0).
In your specific case you would need to create an authorization model like
type object
relations
define reader as self
And then add the following tuples in the FGA store using the Write API:
(user:test, relation:reader, object:1)
(user:test, relation:reader, object:2)
Then, in your API, you would do something like this:
const { Auth0FgaApi } = require('#auth0/fga')
const express = require('express')
const app = express()
const fgaClient = new Auth0FgaApi({
storeId: process.env.FGA_STORE_ID, // Fill this in!
clientId: process.env.FGA_CLIENT_ID, // Fill this in!
clientSecret: process.env.FGA_CLIENT_SECRET // Fill this in!
});
app.get('/objects/:id', async (req, res) => {
try {
const { allowed } = await fgaClient.check({
tuple_key: {
user: req.query.user,
relation: 'reader',
object: "object:" + req.params.id
}
});
if (!allowed) {
res.status(403).send("Unauthorized!")
} else {
res.status(200).send("Authorized!")
}
} catch (error) {
res.status(500).send(error)
}
});
const port = 3000
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

Anyone have a solution for generating server-side tokens for the ESRI JSAPI SDK?

There are a number of solutions to this:
use the build-in dialog provided by esri/IdentityManager (https://developers.arcgis.com/javascript/3/jsapi/identitymanagerbase-amd.html)
use a server-side proxy (https://github.com/Esri/resource-proxy)
use the identity manager initialize() method (https://developers.arcgis.com/javascript/3/jsapi/identitymanagerbase-amd.html#initialize)
But there what is missing is the ability to hook into the request for a token. I am working with ArcGISDynamicMapServiceLayer and there is no way to know if the server return a 498/499, and no way to update the url to update the token.
I started hacking around in the API to try to hook into various events with no real promise of success. What seems to be missing:
a way to detect when a token is needed
a way to update the token
Closes I came up with is listening for "dialog-create" but there is no way to disable the dialog apart from throwing an exception, which disables the layer.
I tried replacing the "_createLoginDialog" method and returning {open: true} as a trick to pause the layers until I had a token ready but since there is no way to update the layer endpoint I did not pursue this hack. It seems the only way this might work is to use the initialize() method on the identity manager.
Does anyone have knowledge of options beyond what I have outlined?
EDIT: The goal is to provide a single-sign-on experience to users of our product.
"User" is already signed in to our application
"User" wishes to access a secure ESRI ArcGIS Server MapServer or FeatureServer services from the ESRI JSAPI
"User" is prompted for user name and password
The desired flow is to acquire a token on the users behalf using a RESTful services in our product and return the appropriate token that will allow the "User" to access the secure services without being prompted.
I do not wish to use a proxy because I do not want all that traffic routed through the proxy.
I do not wish to use initialize() because it is complicated and not clear how that works apart for re-hydrating the credentials.
I do wish for an API that simply allows me to set the token on any layer services that report a 499 (missing token) or 498 (invalid token), but I cannot find any such API. The solution I am focusing on hinges on being able to update the url of an ArcGISImageServiceLayer instance with a new token.
This answer lacks in satisfaction but delivers on my requirements. I will start with the code (client-side typescript):
class TokenProxy {
private tokenAssuranceHash = {} as Dictionary<Promise<{ token: string, expiration: string }>>;
private service = new TokenService();
private timeoutHandle = 0;
watchLayer(esriLayer: ArcGISDynamicMapServiceLayer) {
setInterval(async () => {
const key = esriLayer._url.path;
const token = await this.tokenAssurance(key);
esriLayer._url.query.token = token;
}, 5000);
}
updateRefreshInterval(ticks: number) {
clearTimeout(this.timeoutHandle);
this.timeoutHandle = setTimeout(() => {
Object.keys(this.tokenAssuranceHash).forEach(url => {
this.tokenAssuranceHash[url] = this.service.getMapToken({serviceUrl: url});
});
this.updateRefreshInterval(ticks);
}, ticks);
}
async tokenAssurance(url: string) {
if (!this.tokenAssuranceHash[url]) {
this.tokenAssuranceHash[url] = this.service.getMapToken({serviceUrl: url});
}
try {
const response = await this.tokenAssuranceHash[url];
await this.recomputeRefreshInterval();
return response.token;
} catch (ex) {
console.error(ex, "could not acquire token");
return null;
}
}
async recomputeRefreshInterval() {
const keys = Object.keys(this.tokenAssuranceHash);
if (!keys.length) return;
const values = keys.map(k => this.tokenAssuranceHash[k]);
const tokens = await Promise.all(values);
const min = Math.min(...tokens.map(t => new Date(t.expiration).getTime()));
if (Number.isNaN(min)) return; // error occured, do not update the refresh interval
const nextRefreshInTicks = min - new Date().getTime();
this.updateRefreshInterval(0.90 * nextRefreshInTicks);
}
}
And highlight the hack that makes it work:
const key = esriLayer._url.path;
const token = await this.tokenAssurance(key);
esriLayer._url.query.token = token;
The "_url" is a hidden/private model that I should not be using to update the token but it works.

Why is the callback identifier not being invoked?

I'm trying to implement matching a Kerberos authentication with a local user database in CakePHP4. So I installed CakePHP 4 and the Authentication plugin 2.0. Since Kerberos auth is managed by our IIS WebServer, only thing I have to do is check if the authenticated user is known by my webapp.
The callback authentication should let me implement something like this, right ?
So I put this function in Application.php :
<?php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
// Define where users should be redirected to when they are not authenticated
$service->setConfig([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect',
]);
// Load the authenticators. Session should be first.
$service->loadAuthenticator('Authentication.Session');
$service->loadIdentifier('Authentication.Callback', [
'callback' => function($data) {
// do identifier logic
if (empty($_SERVER['REMOTE_USER'])) {
return new Result(
null,
Result::FAILURE_OTHER,
['message' => 'Unknown user.']
);
} else {
// On vérifie que l'utilisateur est autorisé à utiliser cette application
$users = TableRegistry::getTableLocator()->get('Users');
$remoteUserNoDomain = str_replace("DOMAIN\\", "", $_SERVER['REMOTE_USER']);
$result = $users->find()
->where(['username' => $remoteUserNoDomain]);
if ($result) {
return new Result($result, Result::SUCCESS);
}
return new Result(
null,
Result::FAILURE_OTHER,
['message' => 'Removed user.']
);
}
return null;
}
]);
return $service;
}
But so far, it doesn't seem to work, like it won't call the callback function at all. I tried to put some debug code, exits... Nothing works.
I would assume that you've also done all the other required configuring for authentication to work, ie loading the plugin, adding the authentication middleware, etc.!?
https://book.cakephp.org/authentication/2/en/index.html
That said, identifiers do not do any work on their own, they are being triggered by authenticators in case they actually require them. You only have the Session authenticator loaded, which in its default configuration doesn't make use of identifiers, but even if you configure it to use identifiers (by setting its identify option to true), it will only use them when there already is an identity in the session, then the identifier is being used to validate that identity.
https://github.com/cakephp/authentication/blob/2.3.0/src/Authenticator/SessionAuthenticator.php#L52
I'm not familiar with Kerberos authentication, but if it pre-populates $_SERVER['REMOTE_USER'] (btw. never access superglobals in CakePHP directly, it will only cause trouble down the road), then what you need is a custom authenticator. You could then re-use the password identifier for the ORM access part, as it allows finding something without checking the password (weirdly enough, given its name).
Quick and dirty example based on your snippet:
// src/Authenticator/KerberosAuthenticator.php
namespace App\Authenticator;
use Authentication\Authenticator\AbstractAuthenticator;
use Authentication\Authenticator\Result;
use Authentication\Authenticator\ResultInterface;
use Psr\Http\Message\ServerRequestInterface;
class KerberosAuthenticator extends AbstractAuthenticator
{
public function authenticate(ServerRequestInterface $request): ResultInterface
{
$server = $request->getServerParams();
if (empty($server['REMOTE_USER'])) {
return new Result(null, Result::FAILURE_CREDENTIALS_MISSING);
}
$remoteUserNoDomain = str_replace("DOMAIN\\", "", $server['REMOTE_USER']);
$user = $this->_identifier->identify(['username' => $remoteUserNoDomain]);
if (empty($user)) {
return new Result(
null,
Result::FAILURE_IDENTITY_NOT_FOUND,
$this->_identifier->getErrors()
);
}
return new Result($user, Result::SUCCESS);
}
}
Your service authenticator/identifier setup would then look like this:
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Kerberos');
$service->loadIdentifier('Authentication.Password');
Nore sure if you'd then really want to use the session authenticator like that though, ie whether you only want to identify the remote user once per session.

How do I access the oauth2 authenticated user in a Lumen controller?

I have followed this excellent tutorial Building a Web App with Lumen and OAuth2 for setting up OAuth2 and Lumen. Everything is working fine apart from now I want to access the currently authenticated user information/model.
My route correctly posts the supplied information after I have logged in and I can break with Netbeans inside the controller but I am not clear how to get the user from the underlying Auth framework. I have tried the three methods indicated here Authentication - Laravel but to no avail. The lumen logs shows:
==== routes.php ====
$app->group(['namespace' => 'App\Http\Controllers','prefix' => 'api', 'middleware' => 'oauth'], function($app)
{
$app->post('info', 'InfoController#send');
}
==== InfoController.php ====
namespace App\Http\Controllers;
// the controllers
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Authenticatable;
class InfoController extends Controller{
/* /api/info methods */
public function send(Request $request){
// can iterate over the entire users table but I just
// want the current user (must be some method through
// the authentication stack)
$users = \App\Auth\User::all();
foreach ($users as $user) {
$name = $user->name;
$key = $user->getAuthIdentifier();
$pwd = $user->getAuthPassword();
}
// CODE GETS HERE BUT how to get the current user?
// Authenticated OK (request supplies "Access-Token: Bearer ...")
}
}
This is probably not the cleanest solution and may not match your requirements exactly but it does retrieve the user.
I decided to make another DB query in the proxy to get the user with the same key (in my case, email address) that was requested by the client.
In my case I was sending the user id along with the standard oauth token.
You could use the same technique to set some value in the session.
// ../app/Auth/Proxy.php
namespace App\Auth;
use App\User; // ----- added this line
use GuzzleHttp\Client;
class Proxy {
...
private function proxy($grantType, array $data = [])
{
...
$response = json_decode($guzzleResponse->getBody());
if (property_exists($response, "access_token")) {
...
// added the following line to get the user
$user = User::where('email',$data['username'])->get()->first();
// untested, but you could add the user to your session here
$request = app()->make('request');
$request->session()->put('current_user', $user);
$response = [
'accessToken' => $response->access_token,
'accessTokenExpiration' => $response->expires_in,
'userId' => $user->id,
];
}
...

Authenticate Symfony2 REST API with Google Account

I'm working with Symfony2 and FOSOauthServerBundle in a REST API. I would wish that some user could log in by a client app using their Google Account, for instance.
From my REST server, by web, I can log in with my Google Account (using HWIOauthBundle), but I need to send to the client app an access_token (like FOSOauthServerBundle does).
I'm interested on persist the access_token that Google send to me in my data base and at the same time, send to the client app the json message {'access_token': 'XMekfmns.... } with Google's (and now my REST API too) access_token.
I don't know if my approach is right. Any ideas?
(sorry for my english ;-) )
Thank you very much
This is my (dirty) solution, but I hope people understand what I want to.
From the client side, the user send the request to get authorization from the google account (I'm using Symfony2 with HWIOauthBundle)
http://myserver.com/connect/google
This present the Google login form. The user fill the form and submit it. If success exists, there will be a redirect to myserver.com where the user will logged.
I catch the event onAuthenticationSuccess ...
<?php
namespace App\Bundle\Handler;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\HttpFoundation\Request,
Symfony\Component\HttpFoundation\RedirectResponse,
Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Router;
class SecurityHandler implements AuthenticationSuccessHandlerInterface
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
return new RedirectResponse($this->router->generate('api_get_token', array(
'clientRandomId' => '5ewv02jcis08wsgggk4wow4so0gokco0g4s8kkoc4so4s0gw4c'
)));
}
#clientRandomId value is an existing value in the table (entity) Client ...
#... in the database.
}
... to redirect to a Controller where it will generate an access_token and refresh_token, where they will be saved in the database. At the end, it will be sent a json response to the user, like FOSOauthServerBundle does.
<?php
namespace App\Bundle\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Controller\Controller,
Symfony\Component\HttpFoundation\JsonResponse,
Symfony\Component\Security\Core\Exception\AccessDeniedException;
use FOS\RestBundle\Controller\FOSRestController,
FOS\RestBundle\Controller\Annotations\Route,
FOS\RestBundle\Controller\Annotations\NamePrefix,
FOS\RestBundle\Controller\Annotations\Prefix;
use FOS\RestBundle\Routing\ClassResourceInterface;
use App\Bundle\Entity\AccessToken;
use App\Bundle\Entity\RefreshToken;
class TokenController extends FOSRestController implements ClassResourceInterface {
#this method has the route named 'api_get_token'
public function getAction($clientRandomId)
{
$user = $this->get('security.context')->getToken()->getUser();
#control if user exist ...
#We force the loggout
$this->container->get('security.context')->setToken(null);
$em = $this->get('doctrine')->getManager();
$client = $em->getRepository('AppBundle:Client')->findOneBy(array('randomId' => $clientRandomId));
#control if client exist ...
$expiresAt = time() + 3600;
$accessToken = new AccessToken;
$accessToken->setClient($client);
$accessToken->setToken('access_token'); #This is only an example
$accessToken->setExpiresAt($expiresAt);
$accessToken->setUser($user);
$refreshToken = new RefreshToken;
$refreshToken->setClient($client);
$refreshToken->setToken('refresh_token'); #This is only an example
$refreshToken->setExpiresAt($expiresAt);
$refreshToken->setUser($user);
$em->persist($accessToken);
$em->persist($refreshToken);
$em->flush();
$jsonData = array(
'access_token' => $accessToken->getToken(),
'expires_in' => 3600,
'token_type' => 'bearer',
'scope' => null,
'refresh_token' => $refreshToken->getToken()
);
$response = new JsonResponse($jsonData);
return $response;
}}
I know this is not the best solution but maybe it guide you to a better solution.