5

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__)));
    }
}
  • any luck on this? – Tigris Feb 08 '22 at 03:54
  • Nope, still struggling on it – Pattatharasu Nataraj Feb 08 '22 at 03:55
  • 1
    `after that it is redirecting to login page stating Oauth2 error` - what is redirecting to login page? Your OAuth server, your backend code, your frontend code? What is triggering the redirection? Also, what is the exact error that you get? `After this Its getting failed` - what is the error? What do the server's logs say? How are you calling the token endpoint? To help you answer this we need a bit more information than "it is failing". Maybe you can show relevant error responses, logs, and maybe some code? – Michal Trojanowski Feb 08 '22 at 07:47
  • 1
    Request you to check with fiddler so you can trace where it is failed.if it is callback function which is approaching to call in your main application should be anonymous. Before you do the Network check TLS handshake, most website now a days require pretty secure handshake before providing access to anything, provided the URI being called is all correct. – Anish Sinha Feb 09 '22 at 19:03
  • 1
    Also check hosting instance , sometime it show 2 instance one for main application and owin for others. – Anish Sinha Feb 09 '22 at 19:09
  • @MichalTrojanowski I have tried my best to find the error, we don't have control over the thingsboard application and nothing is there in their log related to oath2, and i have updated the source – Pattatharasu Nataraj Feb 11 '22 at 06:19
  • @AnishSinha have installed fiddler and tested tls handshake is happening properly and there is no error description for failure – Pattatharasu Nataraj Feb 11 '22 at 06:20
  • 1
    please follow below link , i also have faced same issue. yo u may get some insight https://stackoverflow.com/questions/70861411/httpcontext-getowincontext-authentication-getexternallogininfoasync-always-r – Anish Sinha Feb 11 '22 at 08:02
  • 1
    hope you have configured same in okta Single sign on URL: {base uri}/saml2/acs Example: http://localhost:2687/saml2/acs Audience URI (SP Entity ID): {base uri}/saml2 Example: http://localhost:2687/saml2 – Anish Sinha Feb 11 '22 at 08:04
  • 1
    @PattatharasuNataraj in step 1 your `redirect_uri` is wrong.It redirect to thingsboard.io. Update your `redirect_uri` to your app url – Wahyu Kristianto Feb 11 '22 at 15:38
  • @WahyuKristianto, actually it was app URL there i just masked it as thingsboard.io here in post. – Pattatharasu Nataraj Feb 13 '22 at 14:47

1 Answers1

3

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.

Michal Trojanowski
  • 10,641
  • 2
  • 22
  • 41