Azure Function zur Abfrage von E-Mailadressen über die MS GraphAPI

Wir setzen Plumsail Forms vielseitig in Kundenprojekten ein, allerdings können wir dabei nicht direkt auf die MS GraphAPI zugreifen. Praktisch ist da eine Azure Function, die Abhilfe schafft. In diesem Blogartikel erklären wir Ihnen, wie Sie so eine Funktion ganz einfach selbst erstellen können.

Azure Function - Vorbereitungen

Wir möchten eine Azure Function aufbauen, mit der wir aus einer Anwendung heraus abfragen können, ob eine E-Mailadresse im Unternehmen bereits an einen User vergeben ist.

Hierfür nutzen wir die User-Abfrage über die MS GraphAPI: https://developer.microsoft.com/de-de/graph

Als Authentifizierungsmethode verwenden wir Admin Consent. Im späteren Verlauf ist die Zustimmung des Global Admins notwendig, wenn Sie selbst nicht die entsprechenden Berechtigungen besitzen. 

Azure Portal – Einstellungen und Tipps

Als Erstes erstellen wir in Azure eine neue Ressourcengruppe. Dafür loggen wir uns im Azure Portal ein: https://portal.azure.com/

Unter Azure services klicken wir auf „Create a resource“. 

Anschließend wählen wir „Function App“ aus.

Eindeutige Namen vergeben

Der Name der Ressourcengruppe muss tenantweit eindeutig sein, der Name der Function weltweit.

Unser Tipp: Benennen Sie die Ressourcengruppe und die Function App gleich. So bleiben die Daten konsistent und lassen sich besser wiederfinden.

In unserem Beispiel heißt die Ressourcengruppe „GetUserFromGraphAPI“.

Funktionssprache einstellen

Für diesem Blogbeitrag haben wir JavaScript als Funktionssprache genutzt. Sie können auch C#, Python, Java und PowerShell verwenden. Als Runtime stack verwenden wir node.js in der neuesten Version. Außerdem wählen wir noch die Region aus, in der wir uns befinden.

Function App erstellen

In den nächsten Fenstern brauchen Sie keine weiteren Einstellungen vornehmen, da alle Standardwerte beibehalten werden können. Ein Klick auf „Review + Create“ und schon sind Sie fertig. 

Das Erstellen der Ressource dauert ungefähr eine Minute. Sobald diese bereitsteht, können Sie aus der Benachrichtigungsseite heraus mit „Go to Resource“ direkt in die Function App wechseln.

Standardfunktion mit HTTP-Trigger

In unserer Function App erstellen wir links in der Navigation unter „Functions“ über „Functions” weiter über „Add“ eine Standardfunktion mit HTTP-Trigger. Das Authorization Level stellen wir auf Anonymous.

Function App im Azure Portal registrieren

Wir verlassen die Function und kehren ins Azure Portal zurück. Damit die MS GraphAPI abgefragt werden kann, müssen wir die Function App im Azure Portal registrieren und den Zugriff auf die GraphAPI erlauben.

Wir legen über „App registrations“ eine neue Registrierung an.

Auch hier sollte der Name wieder mit dem aus der Function App übereinstimmen. Da der Zugriff aus der späteren Anwendung anonym erfolgt, ist die Einstellung der supported account types hier nebensächlich. Das Feld der Redirect URL lassen wir leer.

Ist die Registrierung abgeschlossen erhält die App eine Client ID. Diese und die Tenant ID sind relevant für unsere Function.

Client Secret für die Authentifizierung gegen die MS GraphAPI

Damit sich unsere Function gegen die MS GraphAPI authentifizieren kann, müssen wir ein Client Secret anlegen, damit. Unter „Certificates & secrets“ wird dieses angelegt.

Komplett sichtbar ist es nur einmal beim Erstellen. Bedeutet: In diesem Moment müssen wir das Secret anderweitig zwischenspeichern.

Durch entsprechende Berechtigungen erlauben wir der Function App, auf Teile der MS GraphAPI zuzugreifen.

Wir verwenden den Endpoint „User“ und die App benötigt folgende Berechtigungen: https://docs.microsoft.com/de-de/graph/api/resources/users?view=graph-rest-1.0

Diese Berechtigungen stellen wir in der App Registrierung unter „API-Permissons“ mit Klick auf „GraphAPI“ ein.

Nun sind alle Vorbereitungen abgeschlossen und wir können die eigentliche Function App entwickeln.

Function App entwickeln

Wir kehren also in die Azure Function zurück. Unter „Development Tools“ klicken wir auf „Console“. Hier erstellen wir mit npm init-y eine blanke package.js und installieren danach mit npm install axios qs nötige npm-Packages. 
Informationen zu npm-Packages finden Sie hier: https://www.npmjs.com/ 

In den Functions unter „App Files“ sollte jetzt eine package.js mit folgendem Inhalt vorhanden sein:

{
  "name": "wwwroot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
"axios": "^0.19.2",
"qs": "^6.9.4"
  }
}

Um die Function später aus einer Anwendung heraus nutzen können, müssen wir Cross-Origin Resource Sharing (CORS) erlauben, indem wir unter „API“ und „CORS “ in das Kästchen bei „Enable Access-Control-Allow-Credentials” ein Häkchen setzen.

Nun zum eigentlichen Code

Azure legt beim Erstellen der Function bereits eine kleine „Hello World“-Anwendung an. Den Code können wir direkt im Browser bearbeiten und testen. Dazu müssen wir unter „Functions“ unsere „GetUserFromGraphAPI“ auswählen und auf „Code + Test“ klicken. Mit dem Button „Run“ starten wir einen ersten Test und bekommen eine entsprechende Antwort.

Die Adresse der Function finden wir unter „Get function URL“ und kopieren sie von dort in die Zwischenablage.

Einen gültigen Access Token generieren

Die MS GraphAPI braucht bei jedem Zugriff einen gültigen Access Token. Dieser wird aus der zuvor erstellten Client ID und dem Client Secret generiert. Wir ersetzen nun den kompletten Code der „Hello World“-Function mit dem folgendem. In die Variablen setzen wir die Werte ein, die wir vorher aus der App Registrierung in die Zwischenablage kopiert haben.

const APP_ID = YOUR APP ID;
const APP_SECERET = YOUR APP SECRET;
const CLIENT_ID = YOUR TENANT ID;
const TOKEN_ENDPOINT = `https://login.microsoftonline.com/${CLIENT_ID}/oauth2/v2.0/token`;
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';

const axios = require('axios');
const qs = require('qs');

const postData = {
  client_id: APP_ID,
  scope: MS_GRAPH_SCOPE,
  client_secret: APP_SECERET,
  grant_type: 'client_credentials',
};

axios.defaults.headers.post['Content-Type'] ='application/x-www-form-urlencoded';

module.exports = async function (context, req) {
  try {
    const response_for_token = await axios.post(TOKEN_ENDPOINT, qs.stringify(postData));
    context.res = {
      status: 200,
      body: JSON.stringify(response_for_token.data.access_token),
    };
  } catch (error) {
    context.log(error);
    context.res = {
            status: 400,
            body: error,
        };
  }
};

Der Token sollte bei einem Testlauf ausgegeben werden.

E-Mailadresse mit der MS GraphAPI überprüfen

Den Token verwenden wir für den Aufruf der MS GrapAPI. Nun können wir über eine, in der URL als Parameter übergebene E-Mailadresse überprüfen, ob diese bereits einem User zugewiesen wurde. Wie oben, ersetzen wir hier wieder den kompletten Code mit den entsprechenden Variablen aus der App Registrierung.

const APP_ID = YOUR APP ID;
const APP_SECERET = YOUR APP SECRET;
const CLIENT_ID = YOUR TENANT ID;
const TOKEN_ENDPOINT = `https://login.microsoftonline.com/${CLIENT_ID}/oauth2/v2.0/token`;
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';

const axios = require('axios');
const qs = require('qs');

const postData = {
  client_id: APP_ID,
  scope: MS_GRAPH_SCOPE,
  client_secret: APP_SECERET,
  grant_type: 'client_credentials',
};

axios.defaults.headers.post['Content-Type'] =
  'application/x-www-form-urlencoded';

module.exports = async function (context, req) {
  try {
    // get access token
    const response_for_token = await axios.post(TOKEN_ENDPOINT, qs.stringify(postData));
    const token = "Bearer " + response_for_token.data.access_token;

    // email address from query
    const address = req.query.address;

    let options = {
        headers: {
            'Authorization': token
        }
    };

    // call GraphAPI
    const response = await axios.get(`https://graph.microsoft.com/v1.0/users/${address}`, options);

    // respond true if a user with the given address exits
    context.res = {
      status: 200,
      body: true,
    };

  } catch (error) {
    context.log(error);

    // respond false but with no error if no user with the givven address exits
    if (error.response.status == 404) {
        context.res = {
            status: 200,
            body: false,
        };
    } else {
        // repond with an error in every other case
        context.res = {
            status: 400,
            boddy: error.response,
        };
    }
   
  }
};

In unserem Beispieltest überprüfen wir in der Registerkarte „Input“, ob die E-Mailadresse info@layer2.de bereits einem User zugeordnet ist.

In der Registerkarte „Output“ bekommen wir dann die Antwort, dass die E-Mailadresse bereits vergeben ist.  

Fertig und einsatzbereit

Unsere Azure Function ist nun fertiggestellt und kann in einer Anwendung zur Validierung verwendet werden.

Eine Anmerkung zur Sicherheit: Auf diesem Weg erstellen wir eine anonym verwendbare Abfrage. Wenn damit auf interne Daten zugegriffen wird, sollten Sie Abfragen ausschließlich in Anwendungen nutzen, deren Code von außen nicht einsehbar ist.