skip to Main Content

I am trying to send an email using Microsft GraphAPI inside my Laravel app. I am able to successfully get an access token through this function.

public function getAccessToken() {
        $tennantId = 'someID';
        $scope = 'https://graph.microsoft.com/.default';
        $appId = 'someAppID';
        $secretId = 'secretID';
        $secret = 'someSecretString';
        $url = "https://login.microsoftonline.com/{$tennantId}/oauth2/v2.0/token?scope={$scope}&client_id={$secretId}";

        $config = [
            'grant_type' => 'client_credentials',
            'client_id' => $appId,
            'client_secret' => $secret,
            'scope' => $scope,
        ];

        $response = Http::asForm()->post($url, $config);

        $data = $response->json();

        $token = Arr::get($data, 'access_token');
        
        return $token;
    }

When I decode the received access token using jwt.io, I get this.

{
  "aud": "https://graph.microsoft.com",
  "iss": "https://sts.windows.net/**********************/",
  "iat": 1727514977,
  "nbf": 1727514977,
  "exp": 1727518877,
  "aio": "k2BgYGiT2uLUtyghU2/p5IaN16YKAAA=",
  "app_displayname": "Mailer Oauth2",
  "appid": "***************************",
  "appidacr": "1",
  "idp": "https://sts.windows.net/***************************/",
  "idtyp": "app",
  "oid": "c4addf3b-ab49-4270-bbf8-a2a4d832c560",
  "rh": "0.ARMBkUlDtYDgK02i7VB0sFJDagMAAAAAAAAAwAAAAAAAAAAUAQA.",
  "roles": [
    "Mail.ReadWrite",
    "Mail.ReadBasic.All",
    "Mail.Read",
    "Mail.Send",
    "Mail.ReadBasic"
  ],
  "sub": "c4addf3b-ab49-4270-bbf8-a2a4d832c560",
  "tenant_region_scope": "EU",
  "tid": "*************************************",
  "uti": "bC3CrECPGEOLobarnNDrAA",
  "ver": "1.0",
  "wids": [
    "0997a1d0-0d1d-4acb-b408-d5ca73121e90"
  ],
  "xms_idrel": "10 7",
  "xms_tcdt": 1727427193
}

Under the roles attribute, I can see that Mail.Send is present.

When I try to send an email using this code

$emailData = [
            'message' => [
                'subject' => [$this->subject],
                'body' => [
                    'contentType' => 'HTML', 
                    'content' => $newTemplate, 
                ]
            ],
                'bccRecipients' => [
                    [
                        'emailAddress' => [
                            'address' => $this->mails->toArray()
                        ]
                    ]
                ],
                'attachments' => [
                    [
                        '@odata.type' => '#microsoft.graph.fileAttachment',
                        'name' => basename($srcImage),
                        'contentBytes' => base64_encode(file_get_contents($srcImage)), 
                        'contentId' => 'image', 
                    ],
                ],
        ];

        
        $response = Http::withToken($this->token)
            ->post("https://graph.microsoft.com/v1.0/users/{$this->email->email}/sendMail", [
                'message' => $emailData['message'],
                'saveToSentItems' => 'true', 
            ]);

But I receive this error

{"error":{"code":"OrganizationFromTenantGuidNotFound","message":"The tenant for tenant guid '***************************' does not exist.","innerError":{"oAuthEventOperationId":"d630a3bf-f7b2-4373-872f-6618f21d7429","oAuthEventcV":"bXHKl4PYUfE8FIF62ShkHA.1.1","errorUrl":"https://aka.ms/autherrors#error-InvalidTenant","requestId":"e131128d-d6f1-43da-85ac-88799cac7cac","date":"2024-09-28T09:30:39"}}}

This is a screenshot from my Azure app (App Permissions tab)
Azure screenshot

I have searched the internet and found out that I should use keyword ‘common’ when optaining an access token, insted of using actual tennantID… I have tried it, but I receive error that tennantID is invalid and I can’t optain access token.

https://login.microsoftonline.com/common/oauth2/v2.0/token?scope={$scope}&client_id={$secretId}

I have selected multitenancy inside the Azure app, as you can see on the screenshot
Azure screenshot

Honestly, I am not very experienced with Microsoft Azure… I assume the problem might be in some Azure definitions, but I have no idea where. Any help is appreciated.

2

Answers


  1. Chosen as BEST ANSWER

    Actually the problem was in the getAccessToken function... I was sending a request to the wrong endpoint, and therefore the obtained access token was wrong. The correct endpoint url is https://login.microsoftonline.com/common/oauth2/v2.0/token More info on this link: https://learn.microsoft.com/en-us/graph/auth-v2-user?tabs=http


  2. A similar question was asked at Microsoft. The top answer is

    Hi @U Anton

    Error 401 Unauthorized ‘OrganizationFromTenantGuidNotFound’ occurs
    when your Azure AD does not have Office 365 account to work.

    If you want to access your messages for your development O365 tenant,
    register the app in your O365 AAD tenant that you got when you created
    the development tenant.

    Please follow the below steps:

    You need Microsoft 365 account with subscription
    In your azure portal login with your Office 365 account
    Create app in Azure active directory under App registration and give permissions according to the documentation
    Then use your messages endpoint for users
    

    You were getting user data users/{users-id} with this API because its
    Azure AD endpoint

    When I decoded your token I found that you were using Application
    token for me/messages endpoint.

    You need delegated token with permissions ‘Mail.ReadBasic, Mail.Read,
    Mail.ReadWrite’ to use ‘me/messages’ endpoint.

    Hope this helps.

    If the answer is helpful, please click "Accept Answer" and kindly
    upvote it. If you have further questions about this answer, please
    click "Comment".

    Another answer says:

    To resolve this issue, I replaced the tenant ID with the keyword
    common in the authorizationURL and tokenURL. This allows users from
    any Azure AD tenant, including personal Microsoft accounts, to
    authenticate. Below is my oAuth strategy Code.

    import { PassportStrategy } from '@nestjs/passport';
    import { Injectable, UnauthorizedException } from '@nestjs/common';
    import { Strategy, VerifyCallback } from 'passport-azure-ad-oauth2';
    import { AuthService } from './auth.service';
    import { ConfigService } from '@nestjs/config';
    import axios from 'axios';
    
    @Injectable()
    export class OauthStrategy extends PassportStrategy(Strategy, 'azure-ad') {
      constructor(
        private readonly authService: AuthService,
        private readonly configService: ConfigService
      ) {
        super({
          clientID: this.configService.get<string>('AZURE_CLIENT_ID'),
          clientSecret: this.configService.get<string>('AZURE_CLIENT_SECRET'),
          callbackURL: this.configService.get<string>('AZURE_CALLBACK_URL'),
          authorizationURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
          tokenURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
          scope: [
            'openid',
            'User.Read',
            'profile',
            'email',
            'Mail.Read',
            'Mail.ReadBasic',
          ],
        });
      }
    
      async validate(
        accessToken: string,
        refreshToken: string,
        params: any,
        profile: any,
        done: VerifyCallback
      ): Promise<any> {
        try {
          const userProfile = await this.getUserProfile(accessToken);
          const user = await this.authService.validateUser(userProfile);
          if (!user) {
            throw new UnauthorizedException();
          }
          done(null, user);
        } catch (error) {
          done(error, false);
        }
      }
    
      async getUserProfile(accessToken: string): Promise<any> {
        const graphUrl = 'https://graph.microsoft.com/v1.0/me';
        const response = await axios.get(graphUrl, {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        });
        return response.data;
      }
    }
    

    So, you will either need to make sure your tenant is properly prepared and then you can pass the id of it like in your URL of "https://login.microsoftonline.com/{$tennantId}/oauth2/v2.0/token?scope={$scope}&client_id={$secretId}", or replace {$tennantId} with common in it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search