import { Issuer, Client } from'openid-client';import {JWT} from'jose';varverifyAccessToken=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 valueconstsettings= { audience:process.env.AAD_TOKEN_APPLICATION_ID, openIdConfigUrl:process.env.AAD_TOKEN_OPEN_ID_CONNECT_METADATA_DOCUMENT, tenantId:process.env.AAD_TOKEN_TENANT_ID };constissuer=awaitIssuer.discover(settings.openIdConfigUrl);constkeyStore=awaitissuer.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 ];}exportdefault { 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 languageconsttypeDefs=gql` type Query { hello: String }`;// Provide resolver functions for your schema fieldsconstresolvers= { Query: {hello: (parent,args,context) => {return`Hello world! ${JSON.stringify(context)}` }, },};// referenced from https://jeffmagnusson.com/post/graphql-apollo-server-plugins-in-typescriptconstappInsightsPlugin= <ApolloServerPlugin&GraphQLRequestListener>{// Fires whenever a GraphQL request is received from a client.requestDidStart(requestContext:GraphQLRequestContext):GraphQLRequestListener|void{appInsightsClient.trackMetric({name:"apollo-query", value:1});returnthis; },// Fires for graph exceptionsdidEncounterErrors:function(requestContext:GraphQLRequestContext) {appInsightsClient.trackMetric({name:"apollo-error", value:1});appInsightsClient.trackException({exception:newError("Apollo Error")});appInsightsClient.trackException({exception: {category:"Apollo Error", details:requestContext.errors}}); }}constgetPlaygroundSetting= () => {if(process.env.APOLLO_PLAYGROUND_VISIBLE==="true" ){if(process.env.APOLLO_PLAYGROUND_ENDPOINT){return {endpoint :process.env.APOLLO_PLAYGROUND_ENDPOINT} }returntrue } else {returnfalse }}constserver=newApolloServer( { typeDefs, resolvers, playground: {endpoint:process.env.APOLLO_PLAYGROUND_ENDPOINT}, plugins: [ appInsightsPlugin ],context:async (request) => {var [ user, validated ] =awaitMsalAuth.VerifyAccessToken(request);return {user, validated}; }, },);constgraphqlHandler=server.createHandler({ cors: { origin:'*', credentials:true, },});exportdefault (context:Context, req:HttpRequest) => {// https://github.com/Azure/azure-functions-host/issues/6013req.headers['x-ms-privatelink-id'] ='';// apollo-server only reads this specific stringreq.headers['Access-Control-Request-Headers'] =req.headers['Access-Control-Request-Headers'] ||req.headers['access-control-request-headers'];returngraphqlHandler(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
Double Calls to Apollo? With React.StrictMode enabled (a good thing) you will notice double calls to Apollo (source) this is actually intended by the React team (source) 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.