0
votes

I've adapted the Java console application letting users sign-in with username/password to call Microsoft Graph API on behalf of them, but instead of retrieving basic user data to send emails.

However, while the original example works fine (I am getting user email, see commented code below), I am getting this error when sending emails:

Graph service exception Error code: NoPermissionsInAccessToken
Error message: The token contains no permissions, or permissions can not be understood.

For this operation Mail.Send scope needs to be defined in Azure portal and it should be sufficient to use without Admin consent. enter image description here

I use these Microsoft dependencies:

<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>msal4j</artifactId>
    <version>1.6.1</version>
</dependency>
<dependency>
    <groupId>com.microsoft.graph</groupId>
    <artifactId>microsoft-graph</artifactId>
    <version>1.6.0</version>
</dependency>

This is the actual code:

public class MicrosoftGraphMailer {

    private final static String CLIENT_APP_ID = "{real-client-app-id}";
    private final static String AUTHORITY = "https://login.microsoftonline.com/{real-tenant-id}/";

    public static void main(String[] args) throws Exception {

        String user = "{real-user}";
        String password = "{real-password}";

        String token = getUserPasswordAccessToken(user, password).accessToken();
        System.out.println(token);

        SimpleAuthProvider authProvider = new SimpleAuthProvider(token);
        IGraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();

        Message message = new Message();
        message.subject = "My subject";

        ItemBody body = new ItemBody();
        body.contentType = BodyType.HTML;
        body.content = "<h1>My HTML body</h1>";
        message.body = body;

        List<Recipient> toRecipientList = new LinkedList<>();
        Recipient toRecipient = new Recipient();
        EmailAddress emailAddress = new EmailAddress();
        emailAddress.address = "{real-recipient}";
        toRecipient.emailAddress = emailAddress;
        toRecipientList.add(toRecipient);
        message.toRecipients = toRecipientList;

        graphClient.me()
                .sendMail(message, false)
                .buildRequest()
                .post();

/*
         String mail = graphClient.me()
                .buildRequest()
                .get().mail;

         System.out.println(mail);
*/
    }

    private static IAuthenticationResult getUserPasswordAccessToken(String user, String password) throws Exception {

        PublicClientApplication app = PublicClientApplication.builder(CLIENT_APP_ID).authority(AUTHORITY).build();

        Set<String> scopes = new HashSet<>(Arrays.asList("Mail.Send"));

        UserNamePasswordParameters userNamePasswordParam = UserNamePasswordParameters.builder(
                scopes, user, password.toCharArray())
                .build();

        return app.acquireToken(userNamePasswordParam).get();
    }

    private static class SimpleAuthProvider implements IAuthenticationProvider {

        private String accessToken = null;

        public SimpleAuthProvider(String accessToken) {
            this.accessToken = accessToken;
        }

        @Override
        public void authenticateRequest(IHttpRequest request) {
            request.addHeader("Authorization", "Bearer " + accessToken);
        }
    }
}

Basically I need console daemon app for sending emails on behalf of me without any user interaction. Credentials will be stored outside the app. I don't need permissions to send emails on behalf of arbitrary user.

1
Did you grant consent for the tenant in the azure portal? Is is step 3.7 in the sample that you are linking to.sgonzalez

1 Answers

0
votes

usually that error message only occurs if its not sending a token to graph or a valid token to graph. To debug, I would first get the token and decode it by either pasting it in jwt.ms or something to see if the required scopes are in the token as expected. I also wonder if not requesting the user.read scope causes issues for mail.send

Also, you said admin consent need not be given, but in your case it does. because if the admin doesn't consent, then the user must consent. but since you are doing this headless, there is no opportunity for the user to consent. meaning no one consented for this application to send mail as you.