Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2-legged OAuth2 using the client_credentials #257

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ then copy the Client ID and Client Secret to the application that will be using
## Authorization

### Obtaining an access token
The first step is to obtain authorization. Mautic supports OAuth 1.0a and OAuth 2 however it is up to the administrator
to decide which is enabled. Thus it is best to have a configuration option within your project for the administrator
The first step is to obtain authorization. Mautic supports OAuth 2. Thus it is best to have a configuration option within your project for the administrator
to choose what method should be used by your code.

```php
Expand All @@ -46,7 +45,6 @@ $callback = '';
// ApiAuth->newAuth() will accept an array of Auth settings
$settings = [
'baseUrl' => '', // Base URL of the Mautic instance
'version' => 'OAuth2', // Version of the OAuth can be OAuth2 or OAuth1a. OAuth2 is the default value.
'clientKey' => '', // Client/Consumer key from Mautic
'clientSecret' => '', // Client/Consumer secret key from Mautic
'callback' => '', // Redirect URI/Callback URI for this script
Expand All @@ -55,7 +53,6 @@ $settings = [
/*
// If you already have the access token, et al, pass them in as well to prevent the need for reauthorization
$settings['accessToken'] = $accessToken;
$settings['accessTokenSecret'] = $accessTokenSecret; //for OAuth1.0a
$settings['accessTokenExpires'] = $accessTokenExpires; //UNIX timestamp
$settings['refreshToken'] = $refreshToken;
*/
Expand All @@ -76,7 +73,6 @@ try {
// refresh token

// $accessTokenData will have the following keys:
// For OAuth1.0a: access_token, access_token_secret, expires
// For OAuth2: access_token, expires, token_type, refresh_token

if ($auth->accessTokenUpdated()) {
Expand All @@ -90,6 +86,41 @@ try {
}
```


### Using 2-legged OAuth2 using Client Credentials

The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user.

```php
<?php

// Bootup the Composer autoloader
include __DIR__ . '/vendor/autoload.php';

use Mautic\Auth\ApiAuth;

$settings = [
'AuthMethod' => 'TwoLeggedOAuth2',
'clientKey' => '',
'clientSecret' => '',
'baseUrl' => '',
];

// $settings['accessToken'] = 'your stored access token';


$initAuth = new ApiAuth();
$auth = $initAuth->newAuth($settings, $settings['AuthMethod']);

if (!isset($settings['accessToken'])) {
// store it for one hour and use it in $settings above
Copy link
Member

@dennisameling dennisameling Sep 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The token isn't always valid for one hour. Mautic users can set the access token lifetime under API settings:

image

... in which case /oauth/v2/token will return an expires_in of, for example, 7200 seconds (2 hrs) instead of 3600 seconds (1 hr):

{
    "access_token": "TOKEN_HERE",
    "expires_in": 7200,
    "token_type": "bearer",
    "scope": null
}

$accessToken = $auth->getAccessToken();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the regular OAuth provider, there's a function called getAccessTokenData() which returns an array with access_token, expires, token_type, refresh_token.

Could we have the same method for TwoLeggedOAuth2 please, to keep things consistent? It can return access_token, expires, token_type. Especially expires is interesting here, because folks can use it to store when the token expires and they need to renew it 😊

You could store the expiration time just like it was done in lib/Auth/OAuth.php 😊

$this->_expires = time() + $params['expires_in'];

}

// Nothing else to do ... It's ready to use.
// Just pass the auth object to the API context you are creating.
```

### Using Basic Authentication Instead
Instead of messing around with OAuth, you may simply elect to use BasicAuth instead.

Expand Down
116 changes: 116 additions & 0 deletions lib/Auth/TwoLeggedOAuth2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

/*
* @copyright 2021 Mautic Contributors. All rights reserved
* @author Mautic, Inc.
*
* @link https://mautic.org
*
* @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
*/

namespace Mautic\Auth;

use Mautic\Exception\RequiredParameterMissingException;

class TwoLeggedOAuth2 extends AbstractAuth
{
/**
* Password associated with Username.
*
* @var string
*/
private $clientSecret;

/**
* Username or email, basically the Login Identifier.
*
* @var string
*/
private $clientKey;

/**
* Access token returned by OAuth server.
*
* @var string
*/
protected $_access_token;

/**
* @var string
*/
private $baseurl;

/**
* @var string
*/
private $_access_token_url;

/**
* {@inheritdoc}
*/
public function isAuthorized()
{
return !empty($this->clientKey) && !empty($this->clientSecret);
}

/**
* @param string $baseUrl
* @param string $clientKey The username to use for Authentication *Required*
* @param string $clientSecret The Password to use *Required*
*
* @throws RequiredParameterMissingException
*/
public function setup($baseUrl, $clientKey, $clientSecret, $accessToken = null)
{
// we MUST have the username and password. No Blanks allowed!
//
// remove blanks else Empty doesn't work
$clientKey = trim($clientKey);
$clientSecret = trim($clientSecret);

if (empty($clientKey) || empty($clientSecret)) {
//Throw exception if the required parameters were not found
$this->log('parameters did not include clientkey and/or clientSecret');
throw new RequiredParameterMissingException('One or more required parameters was not supplied. Both clientKey and clientSecret required!');
}

$this->baseurl = $baseUrl;
$this->clientKey = $clientKey;
$this->clientSecret = $clientSecret;
$this->_access_token = $accessToken;

if (!$this->_access_token_url) {
$this->_access_token_url = $baseUrl.'/oauth/v2/token';
}
}

/**
* @param $url
* @param $method
*
* @return array
*/
protected function prepareRequest($url, array $headers, array $parameters, $method, array $settings)
{
if (null !== $this->_access_token) {
$headers = array_merge($headers, ['Authorization: Bearer '.$this->_access_token]);
}

return [$headers, $parameters];
}

public function getAccessToken(): string
{
$parameters = [
'client_id' => $this->clientKey,
'client_secret' => $this->clientSecret,
'grant_type' => 'client_credentials',
];
$accessTokenData = $this->makeRequest($this->_access_token_url, $parameters, 'POST');
//store access token data however you want
$this->_access_token = $accessTokenData['access_token'] ?? null;

return $this->_access_token;
}
}