Replace Power BI authentication client secret with self-signed certificate in PHP Laravel

153 views Asked by At

I am currently using the following PHP code in Laravel to authenticate and obtain an embed URL for Power BI reports. However, I would like to enhance the security of my Azure AD app by replacing the current client secret-based authentication with a self-signed certificate.

Here is my current code which is working with client secret.

<?php

namespace App\Http\Controllers;

use App\Models\MenuTool;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class PowerBIController extends ApiController
{
    private function getAccessToken()
    {
        $response = Http::asForm()->post("https://login.microsoftonline.com/" . env('PBI_TENANT_ID') . "/oauth2/v2.0/token", [
            'grant_type' => env('PBI_GRANT_TYPE'),
            'client_id' => env('PBI_CLIENT_ID'),
            'client_secret' => env('PBI_CLIENT_SECRET'),
            'scope' => env('PBI_SCOPE'),
        ])->body();

        return $response;
    }

    private function generatePowerBIToken($group_id, $report_id)
    {
        $token = json_decode($this->getAccessToken())->access_token;

        $token_response = Http::asForm()
            ->withHeaders([
                'Content-Type' => 'application/x-www-form-urlencoded',
                'Authorization' => 'Bearer ' . $token,
            ])
            ->post("https://api.powerbi.com/v1.0/myorg/groups/" . $group_id . "/reports/" . $report_id . "/GenerateToken", [
                'grant_type' => env('PBI_GRANT_TYPE'),
            ]);
        $embed_response = $this->getEmbedUrl($token, $group_id, $report_id);
        return ["token" => $token_response->json()['token'], "report_id" => $embed_response['id'], 'embed_url' => $embed_response['embedUrl']];
    }

    private function getEmbedUrl($token, $group_id, $report_id)
    {
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . $token,
        ])
            ->get("https://api.powerbi.com/v1.0/myorg/groups/$group_id/reports/$report_id");
        return $response->json();
    }

    public function index($lang, $slug)
    {
        try {
            $menu = MenuTool::where('slug', $slug)->where('lang', $lang)->first();
            
            if ($menu) {
                if (auth()->user()->isClient()) {
                    $product_ids = array_column(auth()->user()->client->products->toArray(), "id");
                    if (in_array($menu->product_id, $product_ids)) {
                        $token = $this->generatePowerBIToken($menu->group_id, $menu->report_id);
                        return $this->successResponse($token);
                    }

                    return $this->errorResponse("Forbidden", 403);
                } else {
                    $token = $this->generatePowerBIToken($menu->group_id, $menu->report_id);
                    return $this->successResponse($token);
                }
            }
            return $this->errorResponse("Report not found", 404);
        } catch (\Throwable $th) {
            return $this->errorResponse($th->getMessage(), 500);
        }
    }
}

I have already created a self-signed certificate and uploaded it to my Azure AD app. Now, I am struggling with the integration of the certificate-based authentication into the existing code.

Could someone please provide guidance or a code snippet on how to modify the existing code to use the self-signed certificate for authentication instead of the client secret? Any additional steps or considerations for working with certificates in this context would be greatly appreciated.

Thank you in advance!

1

There are 1 answers

1
Naveen Sharma On

Initially, you need to create client assertion for generating access token using client certificate.

In my case, I followed this MS Document and created client assertion successfully like this:

string GetSignedClientAssertion(X509Certificate2 certificate, string tenantId, string clientId)
        {                            
            // no need to add exp, nbf as JsonWebTokenHandler will add them by default.
            var claims = new Dictionary<string, object>()
            {
                { "aud", tokenEndpoint },
                { "iss", clientId },
                { "jti", Guid.NewGuid().ToString() },
                { "sub", clientId }
            };

            var securityTokenDescriptor = new SecurityTokenDescriptor
            {
                Claims = claims,
                SigningCredentials = new X509SigningCredentials(certificate)
            };

            var handler = new JsonWebTokenHandler();
            var signedClientAssertion = handler.CreateToken(securityTokenDescriptor);
        }

Response:

enter image description here

When I used this assertion in below parameters, I got the access token successfully via Postman:

POST https://login.microsoftonline.com/tenantId/oauth2/v2.0/token

grant_type:client_credentials
client_id:appId
scope: https://analysis.windows.net/powerbi/api/.default
client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion:paste_value_from_above

Response:

enter image description here

Using this token, I'm able to call Power BI API and retrieved report details like this:

GET https://api.powerbi.com/v1.0/myorg/groups/groupId/reports/reportId

Response:

enter image description here

In your case, create client assertion similarly and generate access token by modifying below part in your code:

private function getAccessToken()
    {
        $response = Http::asForm()->post("https://login.microsoftonline.com/" . env('PBI_TENANT_ID') . "/oauth2/v2.0/token", [
            'grant_type' => 'client_credentials',
            'client_id' => 'appId',
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
            'client_assertion' => 'generated_assertion_value',
            'scope' => 'https://analysis.windows.net/powerbi/api/.default',
        ])->body();

        return $response;
    }

Reference:

OAuth 2.0 client credentials flow on the Microsoft identity platform