I'm trying to make an Azure B2C login with Laravel, I'm halfway there right now.
The Laravel part is an API, and the client is in Angular. I've planned to make the whole process on the BE. When a user clicks "Sign in with Microsoft" to be redirected to the BE server-side page and I do the magic on the BE. In the end, I want to make a redirect to some FE page and set the access token in the cookie.
I've defined two web.php (server-side) routes. The first one is for redirecting away to the Azure login and the second one is for a callback.
Here is the code:
public function redirect()
{
$length = mt_rand(43, 128);
$bytes = random_bytes($length);
$codeVerifier = rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
$state = Str::random(40);
$query = http_build_query([
'p' => 'B2C_1A_SIGNIN',
'client_id' => config('services.azureadb2c.client_id'),
'redirect_uri' => config('services.azureadb2c.redirect'),
'response_type' => 'code',
'scope' => 'openid',
'state' => $state,
'prompt' => 'login',
'code_challenge' => $this->generateCodeChallenge($codeVerifier),
'code_challenge_method' => 'S256',
]);
session(['state' => $state, 'code_verifier' => $codeVerifier]);
return redirect('https://' . config('services.azureadb2c.domain') . '.b2clogin.com/' . config('services.azureadb2c.domain') . '.onmicrosoft.com/b2c_1a_signin/oauth2/v2.0/authorize?' . $query);
}
public function callback(Request $request)
{
$client = new Client();
$response = $client->post("https://" . config('services.azureadb2c.domain') . ".b2clogin.com/" . config('services.azureadb2c.domain') . ".onmicrosoft.com/b2c_1a_signin/oauth2/v2.0/token?p=B2C_1A_SIGNIN", [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => config('services.azureadb2c.client_id'),
'client_secret' => config('services.azureadb2c.client_secret'),
'code' => $request->get('code'),
'redirect_uri' => config('services.azureadb2c.redirect'),
'code_verifier' => session('code_verifier'),
'scope' => 'openid',
],
]);
var_dump(json_decode($response->body()));
exit;
}
private function generateCodeChallenge($codeVerifier) {
$hash = hash('sha256', $codeVerifier, true);
$codeChallenge = rtrim(strtr(base64_encode($hash), '+/', '-_'), '=');
return $codeChallenge;
}
Here, I'm facing the next problem. Redirect is working well, the user is redirected properly and he can enter the credentials well and everything. At the end in the callback, I receive the authorization code, but when I make a POST request to get the access token I get the next error
`400 Bad Request` response: {"error":"invalid_grant","error_description":"AADB2C90085: The service has encountered an internal error. Please reauthe (truncated...)
Do you have any idea what can cause this error?
I tried to reproduce the same in my environment via Postman and got below results:
I registered one Azure AD B2C application by selecting SPA in redirect URIs like below:
I ran below c# code by Bac Hoang [MSFT] from this blog and got values of code_challenge and code_verifier successfully as below:
using IdentityModel;
using System.Security.Cryptography;
using System.Text;
namespace PKCEConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var random = new Random();
int cvlength = random.Next(43, 128);
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
var codeVerifier = new string (Enumerable.Repeat(chars, cvlength).Select(s => s[random.Next(s.Length)]).ToArray());
string codeChallenge;
using (var sha256 = SHA256.Create())
{
var a = Encoding.UTF8.GetBytes(codeVerifier);
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
codeChallenge = Base64Url.Encode(challengeBytes);
}
Console.WriteLine("codeVerifier " + codeVerifier + "\n");
Console.WriteLine("codeChallenge " + codeChallenge + "\n");
Console.ReadLine();
}
}
}
Response:
Now, I ran below authorization request in browser and got code successfully like this:
https://sritenantb2c.b2clogin.com/sritenantb2c.onmicrosoft.com/B2C_1_Signin/oauth2/v2.0/authorize?
client_id=742978eb-ccb4-4b82-8140-3526417d4d09
&response_type=code
&redirect_uri=https://jwt.ms
&response_mode=query
&scope=openid
&code_challenge=l-kik2UPt2cInWJK6liasiRBhdpNm3qeOqUma_4Ih7E
&code_challenge_method=S256
Response:
When I tried to generate tokens via Postman with below parameters, I got only id_token and refresh_token without access token like below:
POST https://sritenantb2c.b2clogin.com/sritenantb2c.onmicrosoft.com/B2C_1_Signin/oauth2/v2.0/token
client_id:742978eb-ccb4-4b82-8140-xxxxxxxxxx
grant_type:authorization_code
scope:openid
code: <paste code from above step>
redirect_uri: https://jwt.ms
code_verifier:hMl0zyCvyUKJxS1gjOPqFNf1LPQ~tcvekFJeWsfzFGSFMi5~QkpCAq1QHMDT3N
Response:
To get access token, add new scope by exposing an API like below:
Now, add this scope in your application that will come under Delegated permissions like below:
Make sure to grant admin consent to the above scope like below:
Now, generate code again by running below authorization request in browser like this
https://sritenantb2c.b2clogin.com/sritenantb2c.onmicrosoft.com/B2C_1_Signin/oauth2/v2.0/authorize?
client_id=742978eb-ccb4-4b82-8140-xxxxxxxxxx
&response_type=code
&redirect_uri=https://jwt.ms
&response_mode=query
&scope=https://sritenantb2c.onmicrosoft.com/742978eb-ccb4-4b82-8140-xxxxxxxxxx/.default
&code_challenge=l-kik2UPt2cInWJK6liasiRBhdpNm3qeOqUma_4Ih7E
&code_challenge_method=S256
Response:
I generated access token successfully using below parameters via Postman like below:
POST https://sritenantb2c.b2clogin.com/sritenantb2c.onmicrosoft.com/B2C_1_Signin/oauth2/v2.0/token
client_id:742978eb-ccb4-4b82-8140-xxxxxxxxxx
grant_type:authorization_code
scope: https://sritenantb2c.onmicrosoft.com/742978eb-ccb4-4b82-8140-xxxxxxxxxxx/Admin.Read openid
code: <paste code from above step>
redirect_uri: https://jwt.ms
code_verifier:hMl0zyCvyUKJxS1gjOPqFNf1LPQ~tcvekFJeWsfzFGSFMi5~QkpCAq1QHMDT3N
Response:
When I decoded the above access token in jwt.ms, I got the scp claim like below:
Related
I want to connect Instagram Basic Display API with a website in order to display one user's post. But, when trying to authenticate the user (myself, for test purposes) I'm getting this error:
{"error_type": "OAuthException", "code": 400, "error_message": "Invalid redirect_uri"}
This is the PHP code I'm using for my request:
require_once 'ig_defines.php';
Class instagram_basic_display_api {
private $_appId = INSTAGRAM_APP_ID;
private $_appSecret = INSTAGRAM_APP_SECRET;
private $_redirectUrl = INSTAGRAM_APP_REDIRECT_URI;
private $_getCode = '';
private $_apiBaseUrl = 'https://api.instagram.com/';
public $authorizationUrl = '';
// called on class init
function __construct( $params ) {
// save instagram code
$this->_getCode = $params['get_code'];
// get an access token (code will follow)
// get authorization url
$this->_setAuthorizationUrl();
}
private function _setAuthorizationUrl() {
$getVars = array(
'app_id' => $this->_appId,
'redirect_uri' => $this->_redirectUrl,
'scope' => 'user_profile,user_media',
'response_type' => 'code'
);
// create url
$this->authorizationUrl = $this->_apiBaseUrl . 'oauth/authorize?' . http_build_query( $getVars );
}
}
private $_redirectUrl = INSTAGRAM_APP_REDIRECT_URI; is "http://google.com/" as suggested in this post. My OAuth Redirect URI in the facebook developer tools Instagram API settings is "http://google.com/" as well.
For testing I'm using my test webspace. The URL is something like http://mytestwebspace.de/instagram/news_ig_test.php. As you can see, it doesn't have SSL. The final page is going to, though. Could the lack of SSL be an issue?
EDIT: I forgot to add, I did try using http://mytestwebspace.de/instagram/news_ig_test.php as redirectUrl before, but I'm still getting the same error.
I'm not sure what the reason could be.
I can confirm, SSL is required. When using https://mytestwebspace.de/etc... , authentication works just fine. (Though it's not surprising, I didn't quite expect the solution to be that simple, thus why I opened a thread.)
I am integrating Woo-commerce API's in my Laravel 5.6 site using Woo-commerce official rest sdk. I made a link using authentication endpoint URL.Which is mention at here .
When user clicks the link it takes the user to Woo-commerce authentication page, where user login and Approve the request.
After approving the request it should take me to return url which i mention in the link. Instead it shows me the following error.
Error: An error occurred in the request and at the time were unable to send the consumer data.
Here is my code.
`$store_url = 'YourStoreUrl';
$endpoint = '/wc-auth/v1/authorize';
$params = [
'app_name' => 'YourApplicationName',
'scope' => 'read_write',
'user_id' => 'yourUserId',
'return_url' => 'YourStoreUrl/callbackurl',
'callback_url' => 'YourStoreUrl/returncallback'
];
$query_string = http_build_query( $params );
$url = $store_url . $endpoint . '?' . $query_string;`
For future users who may have this same problem, ensure that your callback_url is correct. The error is mostly due to the callback_url.
In my case, I initially had this piece of python code:
store_url = 'http://localhost/wordpress'
endpoint = '/wc-auth/v1/authorize'
params = {
"app_name": "My app",
"scope": "read_write",
"user_id": 123,
"return_url": "http://localhost/wordpress/",
"callback_url": "https://f46c7c857579.ngrok.io",
}
query_string = urlencode(params)
return "%s%s?%s" % (store_url, endpoint, query_string)
But my callback_url was wrong. When I put in the correct callback_url, the error vanished. callback_url": "https://f46c7c857579.ngrok.io/return"
Also, make sure your callback_url has the POST HTTP METHOD
I am using socialite to get the user access_token and use that token to get connect to the Google API Client in laravel. Everything is working fine. But I need to get the access token. But it is failed to get in the response. Let me know how to get the id_token from Google API client.
Here is my code
public function callback()
{
$user = \Socialite::driver('google')->user();
$idToken = $this->getIdToken($user);
var_dump($idToken);
}
public function getIdToken($user){
$google_client_token = [
'access_token' => $user->token,
'refresh_token' => $user->refreshToken,
'expires_in' => $user->expiresIn
];
$client = new Google_Client();
$client->setAccessToken(json_encode($google_client_token));
$oauth = new Google_Service_Oauth2($client);
$client->authenticate($_GET['code']); //exchange 'code' with access token, refresh token and id token
$accessToken = $client->getAccessToken();
$userData = $oauth->userinfo->get();
return $userData;
}
This worked for me, using the methods from Laravel's socialite documentation:
config/services add this to the array (with your own keys)
'google' => [
'client_id' => env('GOOGLE_API_ID'),
'client_secret' => env('GOOGLE_API_SECRET'),
'redirect' => env('APP_URL').'/auth/adwords/callback'
],
Set up your routes as per the docs, and then add these to your class and it will dump out the token and expires_in
public function redirectToProvider() {
return Socialite::with('google')->redirect();
}
public function handleProviderCallback(Request $request) {
$adwords_api_response = Socialite::with('google')->getAccessTokenResponse($request->code);
dd($adwords_api_response);
}
I had a similar issue where coming from an Android device I didn't have access to the access_token so I had to pass an auth_token instead. On the server side here's how I handled it to retrieve an access_token.
$driver = Socialite::driver('google');
//In some cases coming from android an auth token may be required to get an access token
$access_token = $driver->getAccessTokenResponse($input['auth_token'])['access_token'];
$googleUser = $driver->userFromToken($access_token);
I'm currently trying to implement a way to synchronize my PHP App calendar with the Outlook calendar of my clients, using Azure API.
I use OAuth2 and the custom Microsoft provider by Steven Maguire.
I currently run in an issue where I get an error in my response :
{"error":"unsupported_grant_type","error_description":"The provided value for the input parameter 'grant_type' is not valid. Expected values are the following: 'authorization_code', 'refresh_token'."}
I'm having trouble understanding why the grant_type password is not supported, even though it says on the documentation of Azure that it is.
The request looks like this :
client_id=44bef79b-**********************&client_secret=H****************&redirect_uri=https%3A%2F%2F192.168.1.123%2Fmapeyral%2Fcalendarsync.php&grant_type=password&username=******************&password=***********&scope=openid%20profile%20offline_access%20Calendars.ReadWrite
The Authorize url used is : https://login.live.com/oauth20_token.srf
as defined in the Steven Maguire provider.
The header contains the content-type application/x-www-form-urlencoded (I've seen a lot of post where this was what caused the error).
Some of my code :
$this->provider = new Microsoft([
'clientId' => MicrosoftGraphConstants::CLIENT_ID,
'clientSecret' => MicrosoftGraphConstants::CLIENT_SECRET,
'redirectUri' => MicrosoftGraphConstants::REDIRECT_URI,
'urlAuthorize' => MicrosoftGraphConstants::AUTHORITY_URL . MicrosoftGraphConstants::AUTHORIZE_ENDPOINT,
'urlAccessToken' => MicrosoftGraphConstants::AUTHORITY_URL . MicrosoftGraphConstants::TOKEN_ENDPOINT,
'urlResourceOwnerDetails' => MicrosoftGraphConstants::RESOURCE_ID,
'scope' => MicrosoftGraphConstants::SCOPES
]);
if ($_SERVER['REQUEST_METHOD'] === 'GET' && !isset($_GET['code']))
{
// Try getting access token from Database
$workingAccount = $GLOBALS['AppUI']->getState('working_account');
if (isset($workingAccount))
{
// DB access
$DB = new DatabaseConnection();
$dbAccess = $DB->getConnection();
$contactData = DBUserUtils::getContactDataFromEmail($GLOBALS['AppUI']->getState('working_account'), $dbAccess);
// If at least one user contact found
if (!is_null($contactData))
{
// If has refresh token => fill session variables using refresh token
if (!is_null($contactData['contact_refreshToken']))
{
log_msg('debug.log', 'Has refresh token');
$GLOBALS['AppUI']->setState('preferred_username', $contactData['contact_email']);
$GLOBALS['AppUI']->setState('given_name', $contactData['contact_first_name']." ".$contactData['contact_last_name']);
// Get new tokens
$newAccessToken = $this->provider->getAccessToken('refresh_token', [
'refresh_token' => $contactData['contact_refreshToken']
]);
// Update tokens and DB
$GLOBALS['AppUI']->setState('refresh_token', $newAccessToken->getRefreshToken());
$GLOBALS['AppUI']->setState('access_token', $newAccessToken->getToken());
DBOAuthUtils::updateTokenForUser($contactData['contact_id'], $GLOBALS['AppUI']->getState('refresh_token'), $dbAccess);
$this->redirectTo($redirectURL);
}
else
{
$this->getAccessToken();
}
}
else
{
$this->getAccessToken();
}
}
else
{
$this->getAccessToken();
}
function getAccessToken(){
$accessToken = $this->provider->getAccessToken('password', [
'username' => '*************',
'password' => '********',
'scope' => MicrosoftGraphConstants::SCOPES
]);
}
During the first try it doesn't pass the if (isset($workingAccount)) condition (as expected) and go straight to the last else.
Code is a bit ugly for now but I don't think it has an impact on my problem.
Any help would be appreciated !
Thanks
Edit : added code
That helped me, the problem was that I need to use Azure Active Directory and not Azure AD 2.0.
Problem solved !
I'm using the following sections of code to try and authenticate against Facebook using the official PHP SDK:
public function get_login_url() {
$login = $this->facebook->getLoginUrl(array('scope' => 'publish_stream, email, publish_actions','redirect_uri' => 'http://www.sociaspire.com/lib/ajax/facebook-callback.php'));
return $login;
}
public function get_access_token($auth_code) {
$token_request_params = array(
'client_id' => $this->app_id,
'redirect_uri' => "http://www.sociaspire.com/lib/ajax/facebook-callback.php",
'client_secret' => $this->app_key,
'code' => $auth_code,
);
$token_request = $this->facebook->api('oauth/access_token?','GET',$token_request_params);
}
My flow is such that I call get_login_url, navigate to that URL and I am then redirected back to my callback URL, where I should be able to exchange the code Facebook gives for a (short-lived) access token, however when I attempt to get the access token, the API throws:
OAuthException: This authorization code has been used
I'm not sure why, as I haven't made any other requests with the code.
If anybody could help I would be grateful!