🔏
Azure Serverless Quickstart
  • Introduction
  • Initial Setup
    • Workstation Installs
    • Codebase
      • Directory Structure
      • User Interface Project
        • Configuring StoryBook
        • Configure Tailwind
        • Configure Craco
        • -Architectural Decision Log
      • Data Access Project
        • DDD
      • Untitled
      • Full Stack Debugging
      • Creating GitHub Project
    • Infrastructure
      • Configure Session Behavior
      • Create AAD Tenant
      • Resource Group
      • Create AAD B2C Instance
        • Identity Experience Framework
        • Configure Session Behavior
      • Storage Account & CDN
        • CDN Rules
        • Configure Azure BLOB Storage
      • App Insights
        • Create AppInsight Account
        • Apollo GraphQL App Insights Configuration
      • CosmosDB
      • Twilio SendGrid
      • KeyVault
      • Function
      • Function App Settings
      • Front Door
      • DevOps
      • Optional Items
        • Azure Data Factory
      • Azure Event Hub
    • CICD and Source Control
      • Azure DevOps
      • SonarCloud
        • Incorporate into Yaml
      • Chromatic
      • User Interface YAML
      • CICD for Data Access
        • Create Pipeline
        • Data Access YAML
  • Application Structure
    • Connect Apollo
      • Apollo Overview
      • Create Apollo Component
    • MongoDB Integration
      • Mappings
      • Directory Structure
      • Apollo Connection
      • Models
      • Queries Mutations and Subscriptions
      • Caching Reponses
    • Integrating GraphQL Tools
      • GraphQL Code Generator
    • Feature Flags
      • Flag Structure & Storage
      • Website Integration
      • Apollo Integration
      • Tips and Techniques
      • Alternative Approaches
    • React Router
    • Adding Authentication
      • Create AAD Applications
      • Configure AAD For External Identities
      • Adding MSAL And React
      • Add MSAL to the build
      • Add MSAL to ApolloClient
      • Add MSAL to ApolloServer
    • Ant Design
    • Jest Tests
  • Azure Active Directory Business-to-Consumer (AD B2C)
    • Introduction
    • How to navigate through AD B2C documentation
    • Localization
    • Abbreviations
    • Azure AD B2C Extension
  • Cognitive Search
  • Cost Analysis
  • Technical Architecture
    • Identity and Access Control
  • Adding Functionality
    • Google Analytics
      • Create Analytics
    • DAPR
      • DAPR setup
      • DAPR Services (ignore for now)
        • Identity
  • Patterns and Practices
    • Idempotent Messages
    • Pathways
    • DDD
      • Initial Setup
        • Aggregate Root
        • Entity
        • Value Object
      • Field Types
        • Primitive Types
        • Non-Primitive Types
          • Types.DocumentArray
          • PopulatedDoc
          • Custom Types
      • Example Walkthrough
  • Open Items
    • Issue Tracking
  • Helpful Resources
  • DDD
    • Page 1
  • Experimental
    • StaticWebApp
    • Azure Maps
Powered by GitBook
On this page

Was this helpful?

  1. Application Structure
  2. Adding Authentication

Add MSAL to ApolloServer

Install libraries to validate tokens by opening a console in data-access project and issuing the following commands

/azure-quickstart/data-access/
npm install jose
npm install openid-client

In /azure-quickstart/data-access/ create the directories sharedCode/auth .

In the auth directory, create a new file msal.ts and add the following code.

/azure-quickstart/data-access/shared-code/auth/msal.ts
import { Issuer, Client } from 'openid-client';
import {JWT} from 'jose';

var verifyAccessToken = async (context) : Promise<[object, boolean]>  => {
  let token = context.request.headers["authorization"];
  if (!token || !token.startsWith("Bearer ")) return [{}, false];

  token = token.slice(7, token.length).trimLeft(); // Remove 'Bearer ' characters from start of Auth header value

  const settings = {
    audience: process.env.AAD_TOKEN_APPLICATION_ID,
    openIdConfigUrl: process.env.AAD_TOKEN_OPEN_ID_CONNECT_METADATA_DOCUMENT,
    tenantId: process.env.AAD_TOKEN_TENANT_ID
  };

  const issuer = await Issuer.discover(settings.openIdConfigUrl);
  const keyStore = await issuer.keystore();

  var results = JWT.verify(
    token,
    keyStore, 
    {
      audience: settings.audience,
     // issuer: 
        //issuer must remain commented out if you're accepting tokens from :
        // Microsoft's public endpoint (which will be: 'https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0' or it can be any AAD tenant's ID)
        //if you only want to accept local AAD Accounts use: `https://login.microsoftonline.com/${settings.tenantId}/v2.0`
    }
  );
  
  return [
    {
      "authToken": results,
    },
    true
  ];
}

export default {
  VerifyAccessToken: verifyAccessToken
}

Replace the contents of the main function file with the following to properly reference MSAL and to allow for auth headers.

/azure-quickstart/data-access/graphql/index.ts
let appInsights = require("applicationinsights");
appInsights.setup().start();
appInsights.defaultClient.commonProperties = {
  environment: process.env.WEBSITE_HOSTNAME,
  functionArea: "graphql"
};
let appInsightsClient = appInsights.defaultClient;

import {
  ApolloServerPlugin,
  GraphQLRequestContext,
  GraphQLRequestListener,
} from 'apollo-server-plugin-base';

import { ApolloServer, gql }  from 'apollo-server-azure-functions';
import MsalAuth from '../shared/auth/msal';
import { HttpRequest, Context } from '@azure/functions';

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
  Query: {
    hello: (parent,args,context) => {
      return `Hello world! ${JSON.stringify(context)}`
    },
  },
};

// referenced from https://jeffmagnusson.com/post/graphql-apollo-server-plugins-in-typescript
const appInsightsPlugin = <ApolloServerPlugin & GraphQLRequestListener>{

  // Fires whenever a GraphQL request is received from a client.
  requestDidStart(requestContext:GraphQLRequestContext): GraphQLRequestListener | void{
    appInsightsClient.trackMetric({name: "apollo-query", value: 1});
    return this;
  },
  // Fires for graph exceptions
  didEncounterErrors: function(requestContext:GraphQLRequestContext) {
    appInsightsClient.trackMetric({name: "apollo-error", value: 1});
    appInsightsClient.trackException({exception: new Error("Apollo Error")});
    appInsightsClient.trackException({exception: {category:"Apollo Error", details: requestContext.errors}});
  }
  
}

const getPlaygroundSetting = () => {
  if(process.env.APOLLO_PLAYGROUND_VISIBLE === "true" ){
    if(process.env.APOLLO_PLAYGROUND_ENDPOINT){
      return {endpoint :process.env.APOLLO_PLAYGROUND_ENDPOINT}
    }
    return true
  } else {
    return false
  }
}

const server = new ApolloServer(
  { 
    typeDefs, 
    resolvers, 
    playground: {endpoint:process.env.APOLLO_PLAYGROUND_ENDPOINT},
    plugins: [
      appInsightsPlugin
    ],
    context: async (request) => {
      var [ user, validated ] = await MsalAuth.VerifyAccessToken(request);
      return {user, validated};
    },
  },
);

const graphqlHandler = server.createHandler({
  cors: {
    origin: '*',
    credentials: true,
  },
});

export default (context: Context, req: HttpRequest) => {
  // https://github.com/Azure/azure-functions-host/issues/6013
  req.headers['x-ms-privatelink-id'] = '';
  // apollo-server only reads this specific string
  req.headers['Access-Control-Request-Headers'] = req.headers['Access-Control-Request-Headers'] || req.headers['access-control-request-headers'];
  return graphqlHandler(context, req);
}

Update the local settings to supply the function with the appropriate values:

/azure-quickstart/data-access/local.settings.json
{
  ...
  "Values": [
    {
    ...
    "AAD_TOKEN_OPEN_ID_CONNECT_METADATA_DOCUMENT" : "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
    "AAD_TOKEN_APPLICATION_ID" : "<< AAD API APPPLICATION IDENTIFIER>>",
    "AAD_TOKEN_TENANT_ID" : "<< AAD API TENANT ID>>",
    
    "APOLLO_PLAYGROUND_URI" : "http://localhost:7071/api/graphql"
    ...
    }
  ]
  ...
}

Azure Settings

Open the data-access function App

  • In the function app navigate to Configuration

    • Choose + New Application Setting for each of the following

      • Name: AAD_TOKEN_OPEN_ID_CONNECT_METADATA_DOCUMENT

        • Value: <<SAME VALUE AS USED IN LOCAL SETTINGS>>

      • Name: AAD_TOKEN_APPLICATION_ID

        • Value: <<SAME VALUE AS USED IN LOCAL SETTINGS>>

      • Name: AAD_TOKEN_TENANT_ID

        • Value: <<SAME VALUE AS USED IN LOCAL SETTINGS>>

      • Name: APOLLO_PLAYGROUND_URI

        • Value: <<AZURE FRONT DOOR URL>>/api/graphql (e.g.: https://sharethrift<<random number>>.azurefd.net/api/graphql)

  • Choose Save

  • Choose Continue (wait until completion)

Repeat the same steps for the data-access-west function app.

Important Warnings

PreviousAdd MSAL to ApolloClientNextAnt Design

Last updated 4 years ago

Was this helpful?

Double Calls to Apollo? With React.StrictMode enabled (a good thing) you will notice double calls to Apollo () this is actually intended by the React team () to help identify issues to prepare you for React Concurrent, and likely the Apollo team may make changes to better prepare for this. When you compile for production you likely won't see this behavior.

source
source