Trying to implement OAuth2 server and integrate in ThingsBoard platform

Viewed 474

I am trying to implement custom OAuth2 implementation in ThingsBoard instance for that i have implemented OAuth2 server in php from

https://github.com/bshaffer/oauth2-demo-php

http://brentertainment.com/oauth2/

It is working fine as per their demo of bshaffer, when i integrate it with the third party application thingboard instance first step is working fine upto authentication after that it is redirecting to login page stating Oauth2 error but dont know what it is here is the sample URLs and responses of OAuth2 server

http://34.226.xxx.xx/oauth2/web/lockdin/authorize

http://34.226.xxx.xx/oauth2/web/lockdin/token

http://34.226.xxx.xx/oauth2/web/lockdin/resource

Step 1:

http://34.226.xxx.xx/oauth2/web/lockdin/authorize?response_type=code&client_id=demoapp&scope=email&state=2r3TIvWotKyjXJ3Nzef-DA_0EZdJFdMm_bO2wCF7S8%3D&redirect_uri=https://thingsboard.io/login/oauth2/code/

Step 2:

Authorization happens after that with success response it sends back to

https://thingsboard.io/login/oauth2/code/?code=something&state=2r3TIvWotKyjXJ3Nzef-DA_0EZ-dJFdMm_bO2wCF7S8%3D

After this Its getting failed

I have tested the steps after this manaully to generate token and its working fine

http://34.226.xxx.xx/oauth2/web/lockdin/token it gives me response

{
    "access_token": "4a01f8b9e8548420425c8f335eda2a3dbde7ef75",
    "expires_in": 3600,
    "token_type": "Bearer",
    "scope": "email",
    "refresh_token": "ccbb9b8a03949e0e013acdb7f8e79426aa1a0a58"
}

and resource api i get the following response

{
      "email":"test@gmail.com",
      "firstName":"Dave",
      "lastName":"Johnson",
      "profile":1828838378
}

Any help on this will be really appreciated

Edit: Please find the source of end point

Authorize.php

namespace OAuth2Demo\Server\Controllers;
use Silex\Application;
class Authorize
{
    public static function addRoutes($routing)
    {
        $routing->get('/authorize', array(new self(), 'authorize'))->bind('authorize');
        $routing->post('/authorize', array(new self(), 'authorizeFormSubmit'))->bind('authorize_post');
    }
  
    public function authorize(Application $app)
    {
     
        $server = $app['oauth_server'];
        $response = $app['oauth_response'];
        
        if (!$server->validateAuthorizeRequest($app['request'], $response)) {
            return $server->getResponse();
        }

        return $app['twig']->render('server/authorize.twig', array(
            'client_id' => $app['request']->query->get('client_id'),
            'response_type' => $app['request']->query->get('response_type')
        ));
    }
  
    public function authorizeFormSubmit(Application $app)
    {      
        $server = $app['oauth_server'];
        $response = $app['oauth_response'];
        $authorized = (bool) $app['request']->request->get('authorize');
        return $server->handleAuthorizeRequest($app['request'], $response, $authorized);
    }
}

Token.php

namespace OAuth2Demo\Server\Controllers;
use Silex\Application;
class Token
{
    
    public static function addRoutes($routing)
    {
        $routing->post('/token', array(new self(), 'token'))->bind('grant');
    }
    public function token(Application $app)
    {       
        $server = $app['oauth_server'];
        $response = $app['oauth_response'];
        return $server->handleTokenRequest($app['request'], $response);
    }
}

Resource.php

namespace OAuth2Demo\Server\Controllers;    
use Silex\Application;
use Symfony\Component\HttpFoundation\Response;    
class Resource
{       
    public static function addRoutes($routing)
    {
        $routing->get('/resource', array(new self(), 'resource'))->bind('access');
    }
  
    public function resource(Application $app)
    {         
        $server = $app['oauth_server'];
        $response = $app['oauth_response'];

        if (!$server->verifyResourceRequest($app['request'], $response)) {
            return $server->getResponse();
        } else {            
            $api_response = array(
                    "email"=> "xxx@gmail.com",
                     "name"=> "Pattatharasu Nataraj",
                     "family_name"=>"Nataraj",
                     "given_name"=>"Pattatharasu",
                     "middle_name"=>"",
                     "nickname"=>"",
                     "picture"=>"",
                     "updated_at"=>""
            );
            return new Response(json_encode($api_response));
        }
    }
}

Server.php

class Server implements ControllerProviderInterface
{
    
    public function setup(Application $app)
    {
       
        if (!file_exists($sqliteFile = __DIR__.'/../../../data/oauth.sqlite')) {
            $this->generateSqliteDb();
        }
        
        $storage = new Pdo(array('dsn' => 'mysql:host=localhost;dbname=demoapp'));       
        $grantTypes = array(
            'authorization_code' => new AuthorizationCode($storage),
            'user_credentials'   => new UserCredentials($storage),
            'refresh_token'      => new RefreshToken($storage, array(
                'always_issue_new_refresh_token' => true,
            )),
        );
        $server = new OAuth2Server($storage, array(
            'enforce_state' => true,
            'allow_implicit' => true,
            'use_openid_connect' => true,
            'issuer' => $_SERVER['HTTP_HOST'],
        ),$grantTypes);

        $server->addStorage($this->getKeyStorage(), 'public_key');      
        $app['oauth_server'] = $server;
        $app['oauth_response'] = new BridgeResponse();
    }
   
    public function connect(Application $app)
    {          
        $this->setup($app);
        $routing = $app['controllers_factory'];
        Controllers\Authorize::addRoutes($routing);
        Controllers\Token::addRoutes($routing);
        Controllers\Resource::addRoutes($routing);
        return $routing;
    }

    private function generateSqliteDb()
    {
        include_once($this->getProjectRoot().'/data/rebuild_db.php');
    }

    private function getKeyStorage()
    {
        $publicKey  = file_get_contents($this->getProjectRoot().'/data/pubkey.pem');
        $privateKey = file_get_contents($this->getProjectRoot().'/data/privkey.pem');
        $keyStorage = new Memory(array('keys' => array(
            'public_key'  => $publicKey,
            'private_key' => $privateKey,
        )));
        return $keyStorage;
    }

    private function getProjectRoot()
    {
        return dirname(dirname(dirname(__DIR__)));
    }
}
1 Answers

From your description, it looks like the thingsboard application has a problem with exchanging the authorization code for an access token. It looks like your server works fine (as you verified with Postman). You need to find out why thingsboard app can't properly exchange code for tokens. Here's what I would do:

  1. Add some logging to your token endpoint to verify if the thingboard app is reaching your token endpoint or not. If you see the request from thingsboard, check what data is in that request (whether the code is correct, grant_type, other request parameters, the Authorization header, etc.). Verify if that request succeeds or fails in your server. Meaning - log what access tokens you issued and whether you are sending back a 200 response.

  2. If you don't see the request to token endpoint, then make sure that all the configuration in thingsboard app is correct for your instance of an OAuth server:

    • the token endpoint URI, userinfo URI
    • client ID and client secret
    • protocol
    • any hostnames or ports,
    • etc.

You can check their documentation which shows an example of integrating with Auth0: https://thingsboard.io/docs/user-guide/oauth-2-support/ this shows all the things you need to set for your integration.

  1. Should you manage to verify that you receive the token request and return an access token, then make sure that thingsboard is able to call your userinfo endpoint - again check the configuration, especially the URL. Check any mappings that thingsboard is using - make sure that your userifo endpoint returns data in a format recognizable by thingsboard.

  2. If you don't see the token request in your server's logs, then another thing you might want to check is whether your implementation is not changing the state parameter in some way (maybe some redundant encoding is done?). Thingsboard might be rejecting your redirect response if the state parameter doesn't match.

  3. For some reason the documentation asks to check this:

Check the General Settings -> Base URL should not contain “/” at the end (e.g. “http://127.0.0.1:8080” instead of “https://127.0.0.1:8080/”). 

Not sure how would that spoil the OAuth integration, but I reckon it's in the documentation for a reason.

Related