azure functions
241 TopicsExpanding the Public Preview of the Azure SRE Agent
We are excited to share that the Azure SRE Agent is now available in public preview for everyone instantly – no sign up required. A big thank you to all our preview customers who provided feedback and helped shape this release! Watching teams put the SRE Agent to work taught us a ton, and we’ve baked those lessons into a smarter, more resilient, and enterprise-ready experience. You can now find Azure SRE Agent directly in the Azure Portal and get started, or use the link below. 📖 Learn more about SRE Agent. 👉 Create your first SRE Agent (Azure login required) What’s New in Azure SRE Agent - October Update The Azure SRE Agent now delivers secure-by-default governance, deeper diagnostics, and extensible automation—built for scale. It can even resolve incidents autonomously by following your team’s runbooks. With native integrations across Azure Monitor, GitHub, ServiceNow, and PagerDuty, it supports root cause analysis using both source code and historical patterns. And since September 1, billing and reporting are available via Azure Agent Units (AAUs). Please visit product documentation for the latest updates. Here are a few highlights for this month: Prioritizing enterprise governance and security: By default, the Azure SRE Agent operates with least-privilege access and never executes write actions on Azure resources without explicit human approval. Additionally, it uses role-based access control (RBAC) so organizations can assign read-only or approver roles, providing clear oversight and traceability from day one. This allows teams to choose their desired level of autonomy from read-only insights to approval-gated actions to full automation without compromising control. Covering the breadth and depth of Azure: The Azure SRE Agent helps teams manage and understand their entire Azure footprint. With built-in support for AZ CLI and kubectl, it works across all Azure services. But it doesn’t stop there—diagnostics are enhanced for platforms like PostgreSQL, API Management, Azure Functions, AKS, Azure Container Apps, and Azure App Service. Whether you're running microservices or managing monoliths, the agent delivers consistent automation and deep insights across your cloud environment. Automating Incident Management: The Azure SRE Agent now plugs directly into Azure Monitor, PagerDuty, and ServiceNow to streamline incident detection and resolution. These integrations let the Agent ingest alerts and trigger workflows that match your team’s existing tools—so you can respond faster, with less manual effort. Engineered for extensibility: The Azure SRE Agent incident management approach lets teams reuse existing runbooks and customize response plans to fit their unique workflows. Whether you want to keep a human in the loop or empower the Agent to autonomously mitigate and resolve issues, the choice is yours. This flexibility gives teams the freedom to evolve—from guided actions to trusted autonomy—without ever giving up control. Root cause, meet source code: The Azure SRE Agent now supports code-aware root cause analysis (RCA) by linking diagnostics directly to source context in GitHub and Azure DevOps. This tight integration helps teams trace incidents back to the exact code changes that triggered them—accelerating resolution and boosting confidence in automated responses. By bridging operational signals with engineering workflows, the agent makes RCA faster, clearer, and more actionable. Close the loop with DevOps: The Azure SRE Agent now generates incident summary reports directly in GitHub and Azure DevOps—complete with diagnostic context. These reports can be assigned to a GitHub Copilot coding agent, which automatically creates pull requests and merges validated fixes. Every incident becomes an actionable code change, driving permanent resolution instead of temporary mitigation. Getting Started Start here: Create a new SRE Agent in the Azure portal (Azure login required) Blog: Announcing a flexible, predictable billing model for Azure SRE Agent Blog: Enterprise-ready and extensible – Update on the Azure SRE Agent preview Product documentation Product home page Community & Support We’d love to hear from you! Please use our GitHub repo to file issues, request features, or share feedback with the team1.6KViews1like0CommentsOpenAI Agent SDK Integration with Azure Durable Functions
Picture this: Your agent authored with the OpenAI Agent SDK is halfway through analyzing 10,000 customer reviews when it hits a rate limit and dies. All that progress? Gone. Your multi-agent workflow that took 30 minutes to orchestrate? Back to square one because of a rate limit throttle. If you've deployed AI agents in production, you probably know this frustration first-hand. Today, we're announcing a solution that makes your agents reliable: OpenAI Agent SDK Integration with Azure Durable Functions. This integration provides automatic state persistence, enabling your agents to survive any failure and continue exactly where they stopped. No more lost progress, no more starting over, just reliable agents that work. The Challenge with AI Agents Building AI agents that work reliably in production environments has proven to be one of the most significant challenges in modern AI development. As agent sophistication increases with complex workflows involving multiple LLM calls, tool executions, and agent hand-offs, the likelihood of encountering failures increases. This creates a fundamental problem for production AI systems where reliability is essential. Common failure scenarios include: Rate Limiting: Agents halt mid-process when hitting API rate limits during LLM calls Network Timeouts: workflows terminate due to connectivity issues System Crashes: Multi-agent systems fail when individual components encounter errors State Loss: Complex workflows restart from the beginning after any interruption Traditional approaches force developers to choose between building complex retry logic with significant code changes or accepting unreliable agent behavior. Neither option is suitable for production-grade AI systems that businesses depend on and that’s why we’re introducing this integration. Key Benefits of the OpenAI Agent SDK Integration with Azure Durable Functions Our solution leverages durable execution value propositions to address these reliability challenges while preserving the familiar OpenAI Agents Python SDK developer experience. The integration enables agent invocations hosted on Azure Functions to run within durable orchestration contexts where both agent LLM calls and tool calls are executed as durable operations. This integration delivers significant advantages for production AI systems such as: Enhanced Agent Resilience- Built-in retry mechanisms for LLM calls and tool executions enable agents to automatically recover from failures and continue from their last successful step Multi-Agent Orchestration Reliability- Individual agent failures don't crash entire multi-agent workflows, and complex orchestrations maintain state across system restarts Built-in Observability- Monitor agent progress through the Durable Task Scheduler dashboard with enhanced debugging and detailed execution tracking (only applicable when using the Durable Task Scheduler as the Durable Function backend). Seamless Developer Experience- Keep using the OpenAI Agents SDK interface you already know with minimal code changes required to add reliability Distributed Compute and Scalability – Agent workflow automatically scale across multiple compute instances. Core Integration Components: These powerful capabilities are enabled through just a few simple additions to your AI application: durable_openai_agent_orchestrator: Decorator that enables durable execution for agent invocations run_sync: Uses an existing OpenAI Agents SDK API that executes your agent with built-in durability create_activity_tool: Wraps tool calls as durable activities with automatic retry capabilities State Persistence: Maintains agentic workflow state across failures and restarts Hello World Example Let's see how this works in practice. Here's what code written using the OpenAI Agent SDK looks like: import asyncio from agents import Agent, Runner async def main(): agent = Agent( name="Assistant", instructions="You only respond in haikus.", ) result = await Runner.run(agent, "Tell me about recursion in programming.") print(result.final_output) With our added durable integration, it becomes: from agents import Agent, Runner @app.orchestration_trigger(context_name="context") @app.durable_openai_agent_orchestrator # Runs the agent invocation in the context of a durable orchestration def hello_world(context): agent = Agent( name="Assistant", instructions="You only respond in haikus.", ) result = Runner.run_sync(agent, "Tell me about recursion in programming.") # Provides synchronous execution with built-in durability return result.final_output rable Task Scheduler dashboard showcasing the agent LLM call as a durable operation Notice how little actually changed. We added app.durable_openai_agent_orchestrator decorator but your core agent logic stays the same. The run_sync* method provides execution with built-in durability, enabling your agents to automatically recover from failures with minimal code changes. When using the Durable Task Scheduler as your Durable Functions backend, you gain access to a detailed monitoring dashboard that provides visibility into your agent executions. The dashboard displays detailed inputs and outputs for both LLM calls and tool invocations, along with clear success/failure indicators, making it straightforward to diagnose and troubleshoot any unexpected behavior in your agent processes. A note about 'run_sync' In Durable Functions, orchestrators don’t usually benefit from invoking code asynchronously because their role is to define the workflow—tracking state, scheduling activities, and so on—not to perform actual work. When you call an activity, the framework records the decision and suspends the orchestrator until the result is ready. For example, when you call run_sync, the deterministic part of the call completes almost instantly, and the LLM call activity is scheduled for asynchronous execution. Adding extra asynchronous code inside the orchestrator doesn’t improve performance; it only breaks determinism and complicates replay. Reliable Tool Invocation Example For agents requiring tool interactions, there are two implementation approaches. The first option uses the @function_tool decorator from the OpenAI Agent SDK, which executes directly within the context of the durable orchestration. When using this approach, your tool functions must follow durable functions orchestration deterministic constraints. Additionally, since these functions run within the orchestration itself, they may be replayed as part of normal operations, making cost-conscious implementation necessary. from agents import Agent, Runner, function_tool class Weather(BaseModel): city: str temperature_range: str conditions: str @function_tool def get_weather(city: str) -> Weather: """Get the current weather information for a specified city.""" print("[debug] get_weather called") return Weather( city=city, temperature_range="14-20C", conditions="Sunny with wind." ) @app.orchestration_trigger(context_name="context") @app.durable_openai_agent_orchestrator def tools(context): agent = Agent( name="Hello world", instructions="You are a helpful agent.", tools=[get_weather], ) result = Runner.run_sync(agent, input="What's the weather in Tokyo?") return result.final_output The second approach uses the create_activity_tool function, which is designed for non-deterministic code or scenarios where rerunning the tool is expensive (in terms of performance or cost). This approach executes the tool within the context of a durable orchestration activity, providing enhanced monitoring through the Durable Task Scheduler dashboard and ensuring that expensive operations are not unnecessarily repeated during orchestration replays. from agents import Agent, Runner, function_tool class Weather(BaseModel): city: str temperature_range: str conditions: str @app.orchestration_trigger(context_name="context") @app.durable_openai_agent_orchestrator def weather_expert(context): agent = Agent( name="Hello world", instructions="You are a helpful agent.", tools=[ context.create_activity_tool(get_weather) ], ) result = Runner.run_sync(agent, "What is the weather in Tokio?") return result.final_output @app.activity_trigger(input_name="city") async def get_weather(city: str) -> Weather: weather = Weather( city=city, temperature_range="14-20C", conditions="Sunny with wind." ) return weather Leveraging Durable Functions Stateful App Patterns Beyond basic durability of agents, this integration provides access to the full Durable Functions orchestration context, enabling developers to implement sophisticated stateful application patterns when needed, such as: External Event Handling: Use context.wait_for_external_event() for human approvals, external system callbacks, or time-based triggers Fan-out/Fan-in: Coordinate multiple tasks (including sub orchestrations invoking agents) in parallel. Long-running Workflows: Implement workflows that span hours, days, or weeks with persistent state Conditional Logic: Build dynamic agent workflows based on runtime decisions and external inputs Human Interaction and Approval Workflows Example For scenarios requiring human oversight, you can leverage the orchestration context to implement approval workflows: .durable_openai_agent_orchestrator def agent_with_approval(context): # Run initial agent analysis agent = Agent(name="DataAnalyzer", instructions="Analyze the provided dataset") initial_result = Runner.run_sync(agent, context.get_input()) # Wait for human approval before proceeding approval_event = context.wait_for_external_event("approval_received") if approval_event.get("approved"): # Continue with next phase final_agent = Agent(name="Reporter", instructions="Generate final report") final_result = Runner.run_sync(final_agent, initial_result.final_output) return final_result.final_output else: return "Workflow cancelled by user" This flexibility allows you to build sophisticated agentic applications that combine the power of AI agents with enterprise-grade workflow orchestration patterns, all while maintaining the familiar OpenAI Agents SDK experience. Get Started Today This article only scratches the surface of what's possible with the OpenAI Agent SDK integration for Durable Functions The combination of familiar OpenAI Agents SDK patterns with added reliability opens new possibilities for building sophisticated AI systems that can handle real-world production workloads. The integration is designed for a smooth onboarding experience. Begin by selecting one of your existing agents and applying the transformation patterns demonstrated above (often requiring just a few lines of code changes). Documentation: https://aka.ms/openai-agents-with-reliability-docs Sample Applications: https://aka.ms/openai-agents-with-reliability-samples1KViews2likes2CommentsHow to connect Azure SQL database from Python Function App using managed identity or access token
This blog will demonstrate on how to connect Azure SQL database from Python Function App using managed identity or access token. If you are looking for how to implement it in Windows App Service, you may refer to this post: https://techcommunity.microsoft.com/t5/apps-on-azure-blog/how-to-connect-azure-sql-database-from-azure-app-service-windows/ba-p/2873397. Note that Azure Active Directory managed identity authentication method was added in ODBC Driver since version 17.3.1.1 for both system-assigned and user-assigned identities. In Azure blessed image for Python Function, the ODBC Driver version is 17.8. Which makes it possible to leverage this feature in Linux App Service. Briefly, this post will provide you a step to step guidance with sample code and introduction on the authentication workflow. Steps: 1. Create a Linux Python Function App from portal 2. Set up the managed identity in the new Function App by enable Identity and saving from portal. It will generate an Object(principal) ID for you automatically. 3. Assign role in Azure SQL database. Search for your own account and save as admin. Note: Alternatively, you can search for the function app's name and set it as admin, then that function app would own admin permission on the database and you can skip step 4 and 5 as well. 4. Got to Query editor in database and be sure to login using your account set in previous step rather than username and password. Or step 5 will fail with below exception. "Failed to execute query. Error: Principal 'xxxx' could not be created. Only connections established with Active Directory accounts can create other Active Directory users." 5. Run below queries to create user for the function app and alter roles. You can choose to alter part of these roles per your demand. CREATE USER "yourfunctionappname" FROM EXTERNAL PROVIDER; ALTER ROLE db_datareader ADD MEMBER "yourfunctionappname" ALTER ROLE db_datawriter ADD MEMBER "yourfunctionappname" ALTER ROLE db_ddladmin ADD MEMBER "yourfunctionappname" 6. Leverage below sample code to build your own project and deploy to the function app. Sample Code: Below is the sample code on how to use Azure access token when run it from local and use managed identity when run in Function app. The token part needs to be replaced with your own. Basically, it is using "pyodbc.connect(connection_string+';Authentication=ActiveDirectoryMsi')" to authenticate with managed identity. Also, "MSI_SECRET" is used to tell if we are running it from local or function app, it will be created automatically as environment variable when the function app is enabled with Managed Identity. The complete demo project can be found from: https://github.com/kevin808/azure-function-pyodbc-MI import logging import azure.functions as func import os import pyodbc import struct def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') server="your-sqlserver.database.windows.net" database="your_db" driver="{ODBC Driver 17 for SQL Server}" query="SELECT * FROM dbo.users" # Optional to use username and password for authentication # username = 'name' # password = 'pass' db_token = '' connection_string = 'DRIVER='+driver+';SERVER='+server+';DATABASE='+database #When MSI is enabled if os.getenv("MSI_SECRET"): conn = pyodbc.connect(connection_string+';Authentication=ActiveDirectoryMsi') #Used when run from local else: SQL_COPT_SS_ACCESS_TOKEN = 1256 exptoken = b'' for i in bytes(db_token, "UTF-8"): exptoken += bytes({i}) exptoken += bytes(1) tokenstruct = struct.pack("=i", len(exptoken)) + exptoken conn = pyodbc.connect(connection_string, attrs_before = { SQL_COPT_SS_ACCESS_TOKEN:tokenstruct }) # Uncomment below line when use username and password for authentication # conn = pyodbc.connect('DRIVER='+driver+';SERVER='+server+';DATABASE='+database+';UID='+username+';PWD='+ password) cursor = conn.cursor() cursor.execute(query) row = cursor.fetchone() while row: print(row[0]) row = cursor.fetchone() return func.HttpResponse( 'Success', status_code=200 ) Workflow: Below are the workflow in these two authentication ways, with them in mind, we can understand what happened under the hood. Managed Identity: When we enable the managed identify for function app, a service principal will be generated automatically for it, then it follows the same steps as below to authenticate in database. Function App with managed identify -> send request to database with service principal -> database check the corresponding database user and its permission -> Pass authentication. Access Token: The access toke can be generated by executing ‘az account get-access-token --resource=https://database.windows.net/ --query accessToken’ from local, we then hold this token to authenticate. Please note that the default lifetime for the token is one hour, which means we would need to retrieve it again when it expires. az login -> az account get-access-token -> local function use token to authenticate in SQL database -> DB check if the database user exists and if the permissions granted -> Pass authentication. Thanks for reading. I hope you enjoy it.54KViews6likes18CommentsAnnouncing Early Preview: BYO Remote MCP Server on Azure Functions
If you’ve already built Model Context Protocol (MCP) servers with the MCP SDKs and wished you could turn them into world class Remote MCP servers using a hyperscale, serverless platform, then this one’s for you! We’ve published samples showing how to host bring‑your-own (BYO) Remote MCP servers on Azure Functions, so you can run the servers you’ve already built with the MCP SDKs—Python, Node, and .NET—with minimal changes and full serverless goodness. Why this is exciting Keep your code. If you’ve already implemented servers with the MCP SDKs (Python, Node, .NET), deploy them to Azure Functions as remote MCP servers with just one line of code change. Serverless scale when you need it. Functions on the Flex Consumption plan handles bursty traffic, scales out and back to zero automatically, and gives you serverless billing. Secure by default. Your remote server endpoint is protected with function keys out-of- the-box, with option to layer on Azure API Management for added authorization flow. BYO vs. Functions Remote MCP extension—pick the path that fits The BYO option complements the existing Azure Functions MCP extension: Build and host with Functions MCP extension: You can build stateful MCP servers with the MCP tool trigger and binding and host them on Functions. Support for SSE is available today with streamable HTTP coming soon. Host BYO remote MCP Server (this announcement): If you already have a server built with the MCP SDKs, or you prefer those SDKs’ ergonomics, host it as‑is on Functions and keep your current codebase. Either way, you benefit from Functions’ serverless platform: secure access & auth, burst scale, event-driven scale from 0 to N, and pay-for-what-you‑use. What’s supported in this early preview Servers built with the Python, Node, and .NET SDKs Debug locally with func start on Visual Studio or Visual Studio Code; deploy with the Azure Developer CLI (azd up) to get your remote MCP server quickly deployed to Azure Functions Stateless servers using the streamable HTTP transport, with guidance coming soon for stateful servers Hosting on Flex Consumption plan Try it now! Python: https://github.com/Azure-Samples/mcp-sdk-functions-hosting-python Node: https://github.com/Azure-Samples/mcp-sdk-functions-hosting-node .NET: https://github.com/Azure-Samples/mcp-sdk-functions-hosting-dotnet Each repo includes the sample weather MCP server implemented with the MCP SDK for that language. You’ll find instructions on how to run the server locally with Azure Functions Core Tools and deploy with azd up in minutes. Once deployed, you can connect to the remote server from an MCP client. The samples use Visual Studio Code, but other clients like Claude can also be used. Provide feedback to shape feature Tell us what you need next - identity flows, diagnostics, more languages, or any other features. Your feedback will shape how we take this early preview to the next level!1.3KViews3likes0CommentsAnnouncing a flexible, predictable billing model for Azure SRE Agent
Billing for Azure SRE Agent will start on September 1, 2025. Announced at Microsoft Build 2025, Azure SRE Agent is a pre-built AI agent for root cause analysis, uptime improvement, and operational cost reduction. Learn more about the billing model and example scenarios.2.7KViews1like1CommentAnnouncing Native Azure Functions Support in Azure Container Apps
Azure Container Apps is introducing a new, streamlined method for running Azure Functions directly in Azure Container Apps (ACA). This integration allows you to leverage the full features and capabilities of Azure Container Apps while benefiting from the simplicity of auto-scaling provided by Azure Functions. With the new native hosting model, you can deploy Azure Functions directly onto Azure Container Apps using the Microsoft.App resource provider by setting “kind=functionapp” property on the container app resource. You can deploy Azure Functions using ARM templates, Bicep, Azure CLI, and the Azure portal. Get started today and explore the complete feature set of Azure Container Apps, including multi-revision management, easy authentication, metrics and alerting, health probes and many more. To learn more, visit: https://aka.ms/fnonacav25.1KViews2likes1CommentImportant Changes to App Service Managed Certificates: Is Your Certificate Affected?
Overview As part of an upcoming industry-wide change, DigiCert, the Certificate Authority (CA) for Azure App Service Managed Certificates (ASMC), is required to migrate to a new validation platform to meet multi-perspective issuance corroboration (MPIC) requirements. While most certificates will not be impacted by this change, certain site configurations and setups may prevent certificate issuance or renewal starting July 28, 2025. Update (August 5, 2025) We’ve published a Microsoft Learn documentation titled App Service Managed Certificate (ASMC) changes – July 28, 2025 that contains more in-depth mitigation guidance and a growing FAQ section to support the changes outlined in this blog post. While the blog currently contains the most complete overview, the documentation will soon be updated to reflect all blog content. Going forward, any new information or clarifications will be added to the documentation page, so we recommend bookmarking it for the latest guidance. What Will the Change Look Like? For most customers: No disruption. Certificate issuance and renewals will continue as expected for eligible site configurations. For impacted scenarios: Certificate requests will fail (no certificate issued) starting July 28, 2025, if your site configuration is not supported. Existing certificates will remain valid until their expiration (up to six months after last renewal). Impacted Scenarios You will be affected by this change if any of the following apply to your site configurations: Your site is not publicly accessible: Public accessibility to your app is required. If your app is only accessible privately (e.g., requiring a client certificate for access, disabling public network access, using private endpoints or IP restrictions), you will not be able to create or renew a managed certificate. Other site configurations or setup methods not explicitly listed here that restrict public access, such as firewalls, authentication gateways, or any custom access policies, can also impact eligibility for managed certificate issuance or renewal. Action: Ensure your app is accessible from the public internet. However, if you need to limit access to your app, then you must acquire your own SSL certificate and add it to your site. Your site uses Azure Traffic Manager "nested" or "external" endpoints: Only “Azure Endpoints” on Traffic Manager will be supported for certificate creation and renewal. “Nested endpoints” and “External endpoints” will not be supported. Action: Transition to using "Azure Endpoints". However, if you cannot, then you must obtain a different SSL certificate for your domain and add it to your site. Your site relies on *.trafficmanager.net domain: Certificates for *.trafficmanager.net domains will not be supported for creation or renewal. Action: Add a custom domain to your app and point the custom domain to your *.trafficmanager.net domain. After that, secure the custom domain with a new SSL certificate. If none of the above applies, no further action is required. How to Identify Impacted Resources? To assist with the upcoming changes, you can use Azure Resource Graph (ARG) queries to help identify resources that may be affected under each scenario. Please note that these queries are provided as a starting point and may not capture every configuration. Review your environment for any unique setups or custom configurations. Scenario 1: Sites Not Publicly Accessible This ARG query retrieves a list of sites that either have the public network access property disabled or are configured to use client certificates. It then filters for sites that are using App Service Managed Certificates (ASMC) for their custom hostname SSL bindings. These certificates are the ones that could be affected by the upcoming changes. However, please note that this query does not provide complete coverage, as there may be additional configurations impacting public access to your app that are not included here. Ultimately, this query serves as a helpful guide for users, but a thorough review of your environment is recommended. You can copy this query, paste it into Azure Resource Graph Explorer, and then click "Run query" to view the results for your environment. // ARG Query: Identify App Service sites that commonly restrict public access and use ASMC for custom hostname SSL bindings resources | where type == "microsoft.web/sites" // Extract relevant properties for public access and client certificate settings | extend publicNetworkAccess = tolower(tostring(properties.publicNetworkAccess)), clientCertEnabled = tolower(tostring(properties.clientCertEnabled)) // Filter for sites that either have public network access disabled // or have client certificates enabled (both can restrict public access) | where publicNetworkAccess == "disabled" or clientCertEnabled != "false" // Expand the list of SSL bindings for each site | mv-expand hostNameSslState = properties.hostNameSslStates | extend hostName = tostring(hostNameSslState.name), thumbprint = tostring(hostNameSslState.thumbprint) // Only consider custom domains (exclude default *.azurewebsites.net) and sites with an SSL certificate bound | where tolower(hostName) !endswith "azurewebsites.net" and isnotempty(thumbprint) // Select key site properties for output | project siteName = name, siteId = id, siteResourceGroup = resourceGroup, thumbprint, publicNetworkAccess, clientCertEnabled // Join with certificates to find only those using App Service Managed Certificates (ASMC) // ASMCs are identified by the presence of the "canonicalName" property | join kind=inner ( resources | where type == "microsoft.web/certificates" | extend certThumbprint = tostring(properties.thumbprint), canonicalName = tostring(properties.canonicalName) // Only ASMC uses the "canonicalName" property | where isnotempty(canonicalName) | project certName = name, certId = id, certResourceGroup = tostring(properties.resourceGroup), certExpiration = properties.expirationDate, certThumbprint, canonicalName ) on $left.thumbprint == $right.certThumbprint // Final output: sites with restricted public access and using ASMC for custom hostname SSL bindings | project siteName, siteId, siteResourceGroup, publicNetworkAccess, clientCertEnabled, thumbprint, certName, certId, certResourceGroup, certExpiration, canonicalName Scenario 2: Traffic Manager Endpoint Types For this scenario, please manually review your Traffic Manager profile configurations to ensure only “Azure Endpoints” are in use. We recommend inspecting your Traffic Manager profiles directly in the Azure portal or using relevant APIs to confirm your setup and ensure compliance with the new requirements. Scenario 3: Certificates Issued to *.trafficmanager.net Domains This ARG query helps you identify App Service Managed Certificates (ASMC) that were issued to *.trafficmanager.net domains. In addition, it also checks whether any web apps are currently using those certificates for custom domain SSL bindings. You can copy this query, paste it into Azure Resource Graph Explorer, and then click "Run query" to view the results for your environment. // ARG Query: Identify App Service Managed Certificates (ASMC) issued to *.trafficmanager.net domains // Also checks if any web apps are currently using those certificates for custom domain SSL bindings resources | where type == "microsoft.web/certificates" // Extract the certificate thumbprint and canonicalName (ASMCs have a canonicalName property) | extend certThumbprint = tostring(properties.thumbprint), canonicalName = tostring(properties.canonicalName) // Only ASMC uses the "canonicalName" property // Filter for certificates issued to *.trafficmanager.net domains | where canonicalName endswith "trafficmanager.net" // Select key certificate properties for output | project certName = name, certId = id, certResourceGroup = tostring(properties.resourceGroup), certExpiration = properties.expirationDate, certThumbprint, canonicalName // Join with web apps to see if any are using these certificates for SSL bindings | join kind=leftouter ( resources | where type == "microsoft.web/sites" // Expand the list of SSL bindings for each site | mv-expand hostNameSslState = properties.hostNameSslStates | extend hostName = tostring(hostNameSslState.name), thumbprint = tostring(hostNameSslState.thumbprint) // Only consider bindings for *.trafficmanager.net custom domains with a certificate bound | where tolower(hostName) endswith "trafficmanager.net" and isnotempty(thumbprint) // Select key site properties for output | project siteName = name, siteId = id, siteResourceGroup = resourceGroup, thumbprint ) on $left.certThumbprint == $right.thumbprint // Final output: ASMCs for *.trafficmanager.net domains and any web apps using them | project certName, certId, certResourceGroup, certExpiration, canonicalName, siteName, siteId, siteResourceGroup Ongoing Updates We will continue to update this post with any new queries or important changes as they become available. Be sure to check back for the latest information. Note on Comments We hope this information helps you navigate the upcoming changes. To keep this post clear and focused, comments are closed. If you have questions, need help, or want to share tips or alternative detection methods, please visit our official support channels or the Microsoft Q&A, where our team and the community can assist you.22KViews1like1CommentBuilding a TOTP Authenticator App on Azure Functions and Azure Key Vault
Two-factor authentication (2FA) has become a cornerstone of modern digital security, serving as a crucial defense against unauthorized access and account compromises. While many organizations rely on popular authenticator apps like Microsoft Authenticator, there's significant value in understanding how to build and customize your own TOTP (Time-based One-Time Password) solution. This becomes particularly relevant for those requiring specific customizations, enhanced security controls, or seamless integration with existing systems. In this blog, I'll walk through building a TOTP authenticator application using Azure's modern cloud services. Our solution demonstrates using Azure Functions for server-side operations with Azure Key Vault for secrets management. A bonus section covers integrating with Azure Static Web Apps for the frontend. The solution supports the standard TOTP protocol (RFC 6238), ensuring compatibility with services like GitHub and Microsoft's own authentication systems. While this implementation serves as a proof of concept rather than a production-ready system, it provides a solid foundation for understanding how authenticator apps work under the hood. By walking through the core components - from secret management to token generation - it will share valuable insights into both authentication systems and cloud architecture. This knowledge proves especially valuable for teams considering building custom authentication solutions or those looking to better understand the security principles behind 2FA. Understanding TOTP Time-based One-Time Password (TOTP) is an algorithm that generates temporary passwords based on a shared secret key and the current time. The foundation of TOTP lies in its use of a shared secret key. When a user first enables 2FA with a service like GitHub, a unique secret key is generated. This key is then encoded into a QR code that the user scans with their authenticator app. This initial exchange of the secret is the only time it's transmitted between the service and the authenticator. For example, a service will provide a QR code that looks like this: On decoding that, we see that the text encoded within this QR code is: otpauth://totp/Test%20Token?secret=2FASTEST&issuer=2FAS When we break down this URI: otpauth:// specifies this is an OTP authentication totp/ indicates this is time-based (as opposed to counter-based HOTP) Test%20Token is the account name (URL encoded) secret=2FASTEST is the shared secret key issuer=2FAS identifies the service providing the 2FA Once the user scans this, the secret is shared with the app and both the service and authenticator app use it in combination with the current time to generate codes. The process divides time into 30-second intervals. For each interval, the current Unix timestamp is combined with the secret key using a cryptographic hash function (HMAC-SHA1), which produces a consistent 6-digit code that both sides can generate independently. Security in TOTP comes from several key design principles. The short 30-second validity window means that even if an attacker intercepts a code, they have very limited time to use it. The one-way nature of the hash function means that even with a valid code, an attacker cannot work backwards to discover the secret key. Additionally, since the system relies on UTC time, it works seamlessly across different time zones. Most services implement a small amount of time drift tolerance. Since device clocks may not be perfectly synchronized, services typically accept codes from adjacent 30 second time windows. This provides a balance between security and usability, ensuring that slight time differences don't prevent legitimate authentication attempts while maintaining the security benefits of time-based codes. TOTP has become the de facto standard for two-factor authentication across the internet. Its implementation in RFC 6238 ensures compatibility between different services and authenticator apps. This means that whether you're using Google Authenticator, Microsoft Authenticator, or building your own solution like we are, the underlying mechanics remain the same, providing a consistent and secure experience for users. Architecture Our TOTP authenticator is built with security and scalability in mind, leveraging Azure's managed services to handle sensitive authentication data. The system consists of two main components: the web frontend, the backend API, and the secret storage. Backend API: Implemented as Azure Functions, our backend provides endpoints for managing TOTP secrets and generating tokens. We use Azure Functions because they provide excellent security features through managed identities, automatic HTTPS enforcement, and built-in scaling capabilities. The API will contain endpoints for adding new 2FA accounts and retrieving tokens. Secret storage: Azure Key Vault serves as our secure storage for TOTP secrets. This choice provides several crucial benefits: hardware-level encryption for secrets, detailed access auditing, and automatic key rotation capabilities. Azure Key Vault's managed identity integration with Azure Functions ensures secure, certificate-free access to secrets, while its global redundancy guarantees high availability. Prerequisites To follow along this blog, you'll need the following: Azure subscription: You will need an active subscription to host the services we will use. Make sure you have appropriate permissions to create and manage resources. If you don't have one, you can sign up here: https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account Visual Studio Code: For the development environment, install Visual Studio Code. Other IDEs are available, though we will be benefiting from the extensions within this IDE. Download VS Code here: https://code.visualstudio.com/ VS Code Azure extensions (optional): There are many different ways to deploy to Azure Static Web Apps and Azure Functions, but having one-click deploy functionality inside our IDE is extremely useful. To install on VS Code, head to Extensions > Search Azure Static Web Apps > Click Install and do the same for the Azure Functions extension. Building the app Deploying the resources We will need to create at least an Azure Key Vault resource, and if you want to test the Function in the cloud (not just locally) then an Azure Function App too. I've attached the Azure CLI commands to deploy these resources, though it can be done through the portal if that's more comfortable. Firstly, create an Azure Key Vault resource: az keyvault create \ --name <your-kv-name> \ --resource-group <your-rg> \ --location <region> Enable RBAC for your Azure Key Vault: az keyvault update \ --name <your-kv-name> \ --enable-rbac-authorization true Create new Azure Function App: az functionapp create \ --name <app-name> \ --storage-account <storage-name> \ --consumption-plan-location <region> \ --runtime node \ --runtime-version 18 \ --functions-version 4 Set Azure Key Vault name environment variable in Azure Function App: az functionapp config appsettings set \ --name <app-name> \ --resource-group <your-rg> \ --settings "KEY_VAULT_NAME=<your-kv-name>" Grant your Azure Function App's managed identity access to Azure Key Vault: az role assignment create \ --assignee-object-id <function-app-managed-identity> \ --role "Key Vault Secrets Officer" \ --scope /subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.KeyVault/vaults/<your-kv-name> Building the API The backend of our authenticator app serves as the secure foundation for managing 2FA secrets and generating TOTP tokens. While it might be tempting to handle TOTP generation entirely in the frontend (as some authenticator apps do), our cloud-based approach offers several advantages. By keeping secrets server-side, we can provide secure backup and recovery options, implement additional security controls, and protect against client-side vulnerabilities. The backend API will have two key responsibilities which the frontend will trigger: Securely store new account secrets Generating valid TOTP tokens on demand First, we need to create an Azure Functions project in VS Code. The creation wizard will ask you to create a trigger, so let's start with (1) and create a trigger for processing new accounts: Go to Azure tab > Click the Azure Functions icon > Click Create New Project > Choose a folder > Choose JavaScript > Choose Model V4 > Choose HTTP trigger > Provide a name ('accounts') > Click Open in new window. Let's make a few modifications to this base Function: Ensure that the only allowed HTTP method is POST, as there is no need to support both and we will make use of the request body allowed in POST requests. Clear everything inside that function to make way for our upcoming code. Now, let's work forward from this adjusted base: const { app } = require("@azure/functions"); app.http("accounts", { methods: ["POST"], authLevel: "anonymous", handler: async (request, context) => { } }); This accounts endpoint will be responsible for securely storing new TOTP secrets when users add accounts to their authenticator. Here's what we need this endpoint to do: Receive the new account details: the TOTP secret, account name and issuer (extracted from the QR code on the frontend) Validate the request, ensuring proper formatting of all fields and that the user is authenticated Store the secret in Azure Key Vault with appropriate metadata Return success/failure status to allow the frontend to update accordingly. First, let's validate the incoming request data. When setting up two-factor authentication, services provide a QR code containing a URI in the otpauth:// format. This standardized format includes all the information we need to set up TOTP authentication. Assuming the frontend has decoded the QR code and sent us the resulting data, let's add some code to parse and validate this URI format. We'll use JavaScript's built-in URL class to handle the parsing, which will also take care of URL encoding/decoding for us. Add the following code to the function: // First, ensure we have a JSON payload let requestBody; try { requestBody = await request.json(); } catch (error) { context.log('Error parsing request body:', error); return { status: 400, jsonBody: { error: 'Invalid request format', details: 'Request body must be valid JSON containing a TOTP URI' } }; } // Check for the URI in the request const { uri } = requestBody; if (!uri || typeof uri !== 'string') { return { status: 400, jsonBody: { error: 'Missing or invalid TOTP URI', details: 'Request must include a "uri" field containing the TOTP setup URI' } }; } This first section of code handles the basic validation of our incoming request data. We start by attempting to parse the request body as JSON using request.json(), wrapping it in a try-catch block to handle any parsing failures gracefully. If the parsing fails, we return a 400 Bad Request status with a clear error message. After successfully parsing the JSON, we check for the presence of a uri field in the request body and ensure it's a string value. This validation ensures we have the minimum required data before we attempt to parse the actual TOTP URI in the next step. Let's now move on to parsing and validating the TOTP URI itself. This URI should contain all the important information: the type of OTP (TOTP in our case), the account name, the secret key, and optionally the issuer. Here's an example of a valid URI which would be provided by services: otpauth://totp/Test%20Token?secret=2FASTEST&issuer=2FAS To parse this, add the following code after our initial validation: // Parse and validate the TOTP URI try { const totpUrl = new URL(uri); // Validate it's a TOTP URI if (totpUrl.protocol !== "otpauth:") { throw new Error("URI must use otpauth:// protocol"); } if (totpUrl.host !== "totp") { throw new Error("URI must be for TOTP authentication"); } // Extract the components const accountName = decodeURIComponent(totpUrl.pathname.split("/")[1]); const secret = totpUrl.searchParams.get("secret"); const issuer = totpUrl.searchParams.get("issuer"); // Validate required components if (!secret) { throw new Error("Missing secret in URI"); } // Store the parsed data for the next step const validatedData = { accountName, secret, issuer: issuer || accountName, // Fall back to account name if issuer not specified }; ... } catch (error) { context.log("Error validating TOTP URI:", error); return { status: 400, jsonBody: { error: "Invalid TOTP URI", details: error.message, }, }; } We use JavaScript's built-in URL class to do the heavy lifting of parsing the URI components. We first verify this is actually a TOTP URI by checking the protocol and path. Then we extract the three key pieces of information: the account name (from the path), the secret key, and the issuer (both from the query parameters). We validate that the essential secret is present and store all this information in a validatedData object. Now that we have our TOTP data properly validated and parsed, let's move on to setting up our Azure Key Vault integration. Firstly, we must install the required Azure SDK packages: npm install azure/identity azure/keyvault-secrets Now we can add the Azure Key Vault integration to our function. Add these imports at the top of your file: const { DefaultAzureCredential } = require('@azure/identity'); const { SecretClient } = require('@azure/keyvault-secrets'); const { randomUUID } = require('crypto'); // Initialize Key Vault client const credential = new DefaultAzureCredential(); const vaultName = process.env.KEY_VAULT_NAME; const vaultUrl = `https://${vaultName}.vault.azure.net`; const secretClient = new SecretClient(vaultUrl, credential); This code sets up our connection to Azure Key Vault using Azure's managed identity authentication. The DefaultAzureCredential will automatically handle authentication when deployed to Azure, and our vault name comes from an environment variable to keep it configurable. Be sure to go and set the KEY_VAULT_NAME variable inside of your local.settings.json file. Now let's add the code to store our TOTP secret in Azure Key Vault. Add this after our URI validation: // Create a unique name for this secret const secretName = `totp-${Date.now()}`; // Store the secret in Key Vault with metadata try { await secretClient.setSecret(secretName, validatedData.secret, { contentType: 'application/json', tags: { accountName: validatedData.accountName, issuer: validatedData.issuer, type: 'totp-secret' } }); context.log(`Stored new TOTP secret for account ${validatedData.accountName}`); return { status: 201, jsonBody: { message: 'TOTP secret stored successfully', secretName: secretName, accountName: validatedData.accountName, issuer: validatedData.issuer } }; } catch (error) { context.error('Error storing secret in Key Vault:', error); return { status: 500, jsonBody: { error: 'Failed to store TOTP secret' } }; } When storing the secret, we use setSecret with three important parameters: A unique name generated using a UUID (totp-${randomUUID()}). This ensures each secret has a globally unique identifier with no possibility of collisions, even across distributed systems. The resulting name looks like totp-123e4567-e89b-12d3-a456-426614174000. The actual TOTP secret we extracted from the URI. Metadata about the secret, including: contentType marking this as JSON data tags containing the account name and issuer, which helps us identify the purpose of each secret without needing to retrieve its actual value A type tag marking this specifically as a TOTP secret. If the storage succeeds, we return a 201 Created status with details about the stored secret (but never the secret itself). The returned secretName is particularly important as it will be used later when we need to retrieve this secret to generate TOTP codes. Now that we can securely store TOTP secrets, let's create our second endpoint that generates the 6-digit codes. This endpoint will: Retrieve a secret from Azure Key Vault using its unique ID Generate a valid TOTP code based on the current time Return the code along with its remaining validity time Follow the same setup steps as earlier, and ensure you have an empty function. I've named it tokens and set it as a GET request: app.http('tokens', { methods: ['GET'], authLevel: 'anonymous', handler: async (request, context) => { } }); Let's add the code to validate the query parameter and retrieve the secret from Azure Key Vault. A valid request will look like this: /api/tokens?id=totp-123e4567-e89b-12d3-a456-426614174000 We want to ensure the ID parameter exists and has the correct format: // Get the secret ID from query parameters const secretId = request.query.get('id'); // Validate the secret ID format if (!secretId || !secretId.startsWith('totp-')) { return { status: 400, jsonBody: { error: 'Invalid or missing secret ID. Must be in format: totp-{uuid}' } }; } This code first checks if we have a properly formatted secret ID in our query parameters. The ID should start with totp- and be followed by a UUID, matching the format we used when storing secrets in our first endpoint. If the ID is missing or invalid, we return a 400 Bad Request with a helpful error message. Now if the ID is valid, we should attempt to retrieve the secret from Azure Key Vault: try { // Retrieve the secret from Key Vault const secret = await secretClient.getSecret(secretId); ... } catch (error) { context.error('Error retrieving secret:', error); return { status: 500, jsonBody: { error: 'Failed to retrieve secret' } }; } If anything goes wrong during this process (like the secret doesn't exist or we have connection issues), we log the error and return a 500 Internal Server Error. Now that we have the secret from Azure Key Vault, let's add the code to generate the 6-digit TOTP code. First, install otp package: npm install otp Then add this import at the top of your file: const OTP = require('otp'); Now let's generate a 6-digit TOTP using this library from the data retrieved from Azure Key Vault: const totp = new OTP({ secret: secret.value }); // Generate the current token const token = totp.totp(); // Calculate remaining seconds in this 30-second window const timeRemaining = 30 - (Math.floor(Date.now() / 1000) % 30); return { status: 200, jsonBody: { token, timeRemaining } }; Let's break down exactly how this code generates our 6-digit TOTP code. When we generate a TOTP code, we're using our stored secret key to create a unique 6-digit number that changes every 30 seconds. The OTP library handles this through several steps behind the scenes. First, when we create a new OTP instance with new OTP({ secret: secret.value }), we're setting up a TOTP generator with our base32-encoded secret (like 'JBSWY3DPEHPK3PXP') that we retrieved from Azure Key Vault. When we call totp(), the library takes our secret and combines it with the current time to generate a code. It takes the current Unix timestamp, divides it by 30 to get the current time window, then uses this value and our secret in an HMAC-SHA1 operation. The resulting hash is then dynamically truncated to give us exactly 6 digits. This is why anyone with the same secret will generate the same code within the same 30-second window. To help users know when the current code will expire, we calculate timeRemaining by finding out how far we are into the current 30-second window and subtracting that from 30. This gives users a countdown until the next code will be generated. With both our endpoints complete, we now have a functional backend for our TOTP authenticator. The first endpoint securely stores TOTP secrets in Azure Key Vault, generating a unique ID for each one. The second endpoint uses these IDs to retrieve secrets and generate valid 6-digit TOTP codes on demand. This server-side approach offers several advantages over traditional authenticator apps: our secrets are securely stored in Azure Key Vault rather than on user devices, we can easily back up and restore access if needed, and we can add additional security controls around code generation. Testing First, we'll need to run the functions locally using the Azure Functions Core Tools. Open your terminal in the project directory and run: func start I'm using a website designed to check if your 2FA app is working correctly. It creates a valid QR code, and also calculates the TOTP on their end so you can compare results. I highly recommend using this alongside me to test our solution: https://2fas.com/check-token/ It will present you with a QR code. You can scan it in your frontend, though you can copy/paste the below which is the exact same value: otpauth://totp/Test%20Token?secret=2FASTEST&issuer=2FAS Now let's test our endpoints sequentially using curl (or Postman if you prefer). My functions started on port 7071, be sure to check yours before you send the request. Let's start with adding the above secret to Azure Key Vault: curl -X POST http://localhost:7071/api/accounts \ -H "Content-Type: application/json" \ -d '{ "uri": "otpauth://totp/Test%20Token?secret=2FASTEST&issuer=2FAS" }' This should return a response containing the generated secret ID (your UUID will be different): { "message": "TOTP secret stored successfully", "secretName": "totp-f724efb9-a0a7-441f-86c3-2cd36647bfcf", "accountName": "Test Token", "issuer": "2FAS" } Sidenote: If you head to Azure Key Vault in the Azure portal, you can see the saved secret: Now we can use this secretName to generate TOTP codes: curl http://localhost:7071/api/tokens?id=totp-550e8400-e29b-41d4-a716-446655440000 The response will include a 6-digit code and the remaining time until it expires: { "token": "530868", "timeRemaining": 26 } To prove that this is accurate, quickly look again at the website, and you should see the exact same code and a very similar time remaining: This confirms that your code is valid! You can keep generating new codes and checking them - remember that the code changes every 30 seconds, so be quick when testing and validating. Bonus: Frontend UI While not the focus of this blog, as bonus content I've put together a React component which provides a functional interface for our TOTP authenticator. This component allows users to upload QR codes provided by other services, processes them to extract the TOTP URI, sends it to our backend for storage, and then displays the generated 6-digit code with a countdown timer. Here's how it looks: As you can see, I've followed a similar style to other known and modern authenticator apps. I recommend writing your own code for the user interface, as it's highly subjective. However, the following is the full React component in case you can benefit from it: import React, { useState, useEffect, useCallback } from "react"; import { Shield, UserCircle, Plus, Image as ImageIcon } from "lucide-react"; import jsQR from "jsqr"; const TOTPAuthenticator = () => { const [secretId, setSecretId] = useState(null); const [token, setToken] = useState(null); const [timeRemaining, setTimeRemaining] = useState(null); const [localTimer, setLocalTimer] = useState(null); const [error, setError] = useState(null); const [isPasting, setIsPasting] = useState(false); useEffect(() => { let timerInterval; if (timeRemaining !== null) { setLocalTimer(timeRemaining); timerInterval = setInterval(() => { setLocalTimer((prev) => { if (prev <= 0) return timeRemaining; return prev - 1; }); }, 1000); } return () => clearInterval(timerInterval); }, [timeRemaining]); const processImage = async (imageData) => { try { const img = new Image(); img.src = imageData; await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; }); const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; context.drawImage(img, 0, 0); const imgData = context.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imgData.data, canvas.width, canvas.height); if (!code) { throw new Error("No QR code found in image"); } const response = await fetch( "http://localhost:7071/api/accounts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ uri: code.data }), } ); const data = await response.json(); if (!response.ok) throw new Error(data.error); setSecretId(data.secretName); setToken({ issuer: data.issuer, accountName: data.accountName, code: "--", }); setError(null); } catch (err) { setError(err.message); } finally { setIsPasting(false); } }; const handlePaste = useCallback(async (e) => { e.preventDefault(); setIsPasting(true); setError(null); try { const items = e.clipboardData.items; const imageItem = Array.from(items).find((item) => item.type.startsWith("image/") ); if (!imageItem) { throw new Error("No image found in clipboard"); } const blob = imageItem.getAsFile(); const reader = new FileReader(); reader.onload = async (event) => { await processImage(event.target.result); }; reader.onerror = () => { setError("Failed to read image"); setIsPasting(false); }; reader.readAsDataURL(blob); } catch (err) { setError(err.message); setIsPasting(false); } }, []); const handleDrop = useCallback(async (e) => { e.preventDefault(); setIsPasting(true); setError(null); try { const file = e.dataTransfer.files[0]; if (!file || !file.type.startsWith("image/")) { throw new Error("Please drop an image file"); } const reader = new FileReader(); reader.onload = async (event) => { await processImage(event.target.result); }; reader.onerror = () => { setError("Failed to read image"); setIsPasting(false); }; reader.readAsDataURL(file); } catch (err) { setError(err.message); setIsPasting(false); } }, []); const handleDragOver = (e) => { e.preventDefault(); }; useEffect(() => { let interval; const fetchToken = async () => { try { const response = await fetch( `http://localhost:7071/api/tokens?id=${secretId}` ); const data = await response.json(); if (!response.ok) throw new Error(data.error); setToken((prevToken) => ({ ...prevToken, code: data.token, })); setTimeRemaining(data.timeRemaining); const nextFetchDelay = data.timeRemaining * 1000 || 30000; interval = setTimeout(fetchToken, nextFetchDelay); } catch (err) { setError(err.message); interval = setTimeout(fetchToken, 30000); } }; if (secretId) { fetchToken(); } return () => clearTimeout(interval); }, [secretId]); if (!secretId) { return ( <div className="w-[416px] max-w-full mx-auto bg-white rounded-xl shadow-md overflow-hidden"> <div className="bg-[#0078D4] p-4 text-white flex items-center gap-2"> <Shield className="mt-px" size={24} /> <h2 className="text-xl font-semibold m-0">My Authenticator</h2> </div> <div className="p-6"> <div className={`w-full p-10 border-2 border-dashed border-gray-300 rounded-lg text-center cursor-pointer transition-all duration-200 ${ isPasting ? "bg-gray-100" : "bg-white" }`} onPaste={handlePaste} onDrop={handleDrop} onDragOver={handleDragOver} tabIndex={0} > <ImageIcon size={32} className="text-gray-600 mx-auto" /> <p className="text-gray-600 mt-3 text-sm"> {isPasting ? "Processing..." : "Paste or drop QR code here"} </p> </div> {error && <div className="text-red-600 text-sm mt-2">{error}</div>} </div> </div> ); } return ( <div className="w-[416px] max-w-full mx-auto bg-white rounded-xl shadow-md overflow-hidden"> <div className="bg-[#0078D4] p-4 text-white flex items-center gap-2"> <Shield className="mt-px" size={24} /> <h2 className="text-xl font-semibold m-0">My Authenticator</h2> </div> <div className="flex items-center p-4 border-b"> <div className="bg-gray-100 rounded-full w-10 h-10 flex items-center justify-center mr-4"> <UserCircle size={24} className="text-gray-600" /> </div> <div className="flex-1"> <h3 className="text-base font-medium text-gray-800 m-0"> {token?.issuer || "--"} </h3> <p className="text-sm text-gray-600 mt-1 m-0"> {token?.accountName || "--"} </p> </div> <div className="text-right"> <p className="text-2xl font-medium text-gray-800 m-0 mb-0.5"> {token?.code || "--"} </p> <p className="text-xs text-gray-600 m-0"> {localTimer || "--"} seconds </p> </div> </div> <div className="p-6"> <div className={`w-full p-10 border-2 border-dashed border-gray-300 rounded-lg text-center cursor-pointer transition-all duration-200 ${ isPasting ? "bg-gray-100" : "bg-white" }`} onPaste={handlePaste} onDrop={handleDrop} onDragOver={handleDragOver} tabIndex={0} > <ImageIcon size={32} className="text-gray-600 mx-auto" /> <p className="text-gray-600 mt-3 text-sm"> {isPasting ? "Processing..." : "Paste or drop QR code here"} </p> </div> </div> {error && <div className="text-red-600 text-sm mt-2">{error}</div>} </div> ); }; export default TOTPAuthenticator; For deployment, I recommend Azure Static Web Apps because it offers built-in authentication, global CDN distribution, and seamless integration with our Azure Functions backend. Summary In this blog, we've built a TOTP authenticator that demonstrates both the inner workings of two-factor authentication and modern cloud architecture. We've demystified how TOTP actually works - from the initial QR code scanning and secret sharing, to the time-based algorithm that generates synchronized 6-digit codes. By implementing this ourselves using Azure services like Azure Key Vault and Azure Functions, we've gained deep insights into both the security protocol and cloud-native development. While this implementation focuses on the core TOTP functionality, it serves as a foundation that you can build upon with features like authenticated multi-user support, backup codes, or audit logging. Whether you're interested in authentication protocols, cloud architecture, or both, this project provides hands-on experience with real-world security implementations. The complete source code for this project is available on my GitHub repository: https://github.com/stephendotgg/azure-totp-authenticator Thanks for reading! Hopefully this has helped you understand TOTP and Azure services better.1.9KViews1like1CommentAnnouncing the public preview launch of Azure Functions durable task scheduler
We are excited to roll out the public preview of the Azure Functions durable task scheduler. This new Azure-managed backend is designed to provide high performance, improve reliability, reduce operational overhead, and simplify the monitoring of your stateful orchestrations. If you missed the initial announcement of the private preview, see this blog post. Durable Task Scheduler Durable functions simplifies the development of complex, stateful, and long-running apps in a serverless environment. It allows developers to orchestrate multiple function calls without having to handle fault tolerance. It's great for scenarios like orchestrating multiple agents, distributed transactions, big data processing, batch processing like ETL (extract, transform, load), asynchronous APIs, and essentially any scenario that requires chaining function calls with state persistence. The durable task scheduler is a new storage provider for durable functions, designed to address the challenges and gaps identified by our durable customers with existing bring-your-own storage options. Over the past few months, since the initial limited early access launch of the durable task scheduler, we’ve been working closely with our customers to understand their requirements and ensure they are fully supported in using the durable task scheduler successfully. We’ve also dedicated significant effort to strengthening the fundamentals – expanding regional availability, solidifying APIs, and ensuring the durable task scheduler is reliable, secure, scalable, and can be leveraged from any of the supported durable functions programming languages. Now, we’re excited to open the gates and make the durable task scheduler available to the public. Some notable capabilities and enhancements over the existing “bring your own storage” options include: Azure Managed Unlike the other existing storage providers for durable functions, the durable task scheduler offers dedicated resources that are fully managed by Azure. You no longer need to bring your own storage account for storing orchestration and entity state, as it is completely built in. Looking ahead, the roadmap includes additional operational capabilities, such as auto-purging old execution history, handling failover, and other Business Continuity and Disaster Recovery (BCDR) capabilities. Superior Performance and Scalability Enhanced throughput for processing orchestrations and entities, ideal for demanding and high-scale applications. Efficiently manages sudden bursts of events, ensuring reliable and quick processing of your orchestrations across your function app instances. The table below compares the throughput of the durable task scheduler provider and the Azure Storage provider. The function app used for this test runs onone to four Elastic Premium EP2 instances. The orchestration code was written in C# using the .NET Isolated worker model on NET 8. The same app was used for all storage providers, and the only change was the backend storage provider configuration. The test is triggered using an HTTP trigger which starts 5,000 orchestrations concurrently. The benchmark used a standard orchestrator function calling five activity functions sequentially, each returning a "Hello, {cityName}!" string. This specific benchmark showed that the durable task scheduler is roughly five times faster than the Azure Storage provider. Orchestration Debugging and Management Dashboard Simplify the monitoring and management of orchestrations with an intuitive out-of-the-box UI. It offers clear visibility into orchestration errors and lifecycle events through detailed visual diagrams, providing essential information on exceptions and processing times. It also enables interactive orchestration management, allowing you to perform ad hoc actions such as suspending, resuming, raising events, and terminating orchestrations. Monitor the inputs and outputs between orchestration and activities. Exceptions surfaced making it easy to identify where and why an orchestration may have failed. Security Best Practices Uses identity-based authentication with Role-Based Access Control (RBAC) for enterprise-grade authorization, eliminating the need for SAS tokens or access keys. Local Emulator To simplify the development experience, we are also launching a durable task scheduler emulator that can be run as a container on your development machine. The emulator supports the same durable task scheduler runtime APIs and stores data in local memory, enabling a completely offline debugging experience. The emulator also allows you to run the durable task scheduler management dashboard locally. Pricing Plan We’re excited to announce the initial launch of the durable task scheduler with a Dedicated, fixed pricing plan. One of the key pieces of feedback that we’ve consistently received from customers is the desire for more upfront billing transparency. To address this, we’ve introduced a fixed pricing model with the option to purchase a specific amount of performance and storage through an abstraction called a Capacity Unit (CU). A single CU provides : Single tenancy with dedicated resources for predictable performance Up to 2,000 work items* dispatched per second 50GB of orchestration data storage A Capacity Unit (CU) is a measure of the resources allocated to your durable task scheduler. Each CU represents a pre-allocated amount of CPU, memory, and storage resources. A single CU guarantees the dispatch of a certain number of work items and provides a defined amount of storage. If additional performance and/or storage are needed, more CUs can be purchased*. A Work Item is a message dispatched by the durable task scheduler to your application, triggering the execution of orchestrator, activity, or entity functions. The number of work items that can be dispatch per second is determined by the Capacity Units allocated to the durable task scheduler. For detailed instructions on determining the number of work items your applications needs and the number of CUs you should purchase, please refer to the guidance provided here. *At the beginning of the public preview phase, schedulers will be temporarily limited to a single CU. *Billing for the durable task scheduler will begin on May 1st, 2025. Under the Hood The durable functions team has been continuously evolving the architecture of the backends that persist the state of orchestrations and entities; the durable task scheduler is the latest installment in this series, and it includes both the most successful characteristics of its predecessors, as well as some significant improvements of its own. In the next paragraph, we shed some light on what is new. Of course, it is not necessary to understand these internal implementation details, and they are subject to change as we will keep improving and optimizing the design. Like the MSSQL provider, the durable task scheduler uses a SQL database as the storage foundation, to provide robustness and versatility. Like the Netherite provider, it uses a partitioned design to achieve scale-out, and a pipelining optimization to boost the partition persistence. Unlike the previous backends, however, the durable task scheduler runs as a service, on its own compute nodes, to which workers are connected by GRPC. This significantly improves latency and load balancing. It strongly isolates the workflow management logic from the user application, allowing them to be scaled separately. What can we expect next for the durable task scheduler? One of the most exciting developments is the significant interest we’ve received in leveraging the durable task scheduler across other Azure compute offerings beyond Azure Functions, such as Azure Container Apps (ACA) and Azure Kubernetes Service (AKS). As we continue to enhance the integration with durable functions, we have also integrated the durable task SDKs, which are the underlying technology behind the durable task framework and durable functions, to support the durable task scheduler directly. We refer to these durable task sdks as the “portable SDKs” because they are a client-only SDK that connects directly to the durable task scheduler, where the managed orchestration engine resides, eliminating any dependency on the underlying compute platform, hence the name “portable”. By utilizing the portable SDKs to author your orchestrations as code, you can deploy your orchestrations across any Azure compute offering. This allows you to leverage the durable task scheduler as the backend, benefiting from its full set of capabilities. If you would like to discuss this further with our team or are interested in trying out the portable SDK yourself, please feel free to reach out to us at DurableTaskScheduler@microsoft.com . We welcome your questions and feedback. We've also received feedback from customers’ requesting a versioning mechanism to facilitate zero downtime deployments. This feature would enable you to manage breaking workflow changes by allowing all in-flight orchestrations using the older version to complete, while switching new orchestrations to the updated version. This is already in development and will be available in the near future. Lastly, we are in the process of introducing critical enterprise features under the category of Business Continuity and Disaster Recovery (BCDR). We understand the importance of these capabilities as our customers rely on the durable task scheduler for critical production scenarios. Get started with the durable task scheduler Migrating to the durable task scheduler from an existing durable function application is a quick process. The transition is purely configuration changes, meaning your existing orchestrations and business logic remain unchanged. The durable task scheduler is provided through a new Azure resource known as a scheduler. Each scheduler can contain one or multiple task hubs, which are sub-resources. A task hub, an established concept within durable functions, is responsible for managing the state and execution of orchestrations and activities. Think of a task hub as a logical way to separate your applications that require orchestrations execution. A Durable Task scheduler resource in the Azure Portal includes a task hub named dts-github-agent One you have created a scheduler and task hub(s), simply add the library package to your project and update your host.json to point your function app to the durable task scheduler endpoint and task hub. That’s all there is to it. With the correct authentication configuration applied, your applications can fully leverage the capabilities of the durable task scheduler. For more detailed information on how to migrate or start using the durable task scheduler, visit our getting started page here. Get in touch to learn more We are always interested in engaging with both existing and potential new customers. If any of the above interests you,, if you have any questions, or if you simply want to discuss your scenarios and explore how you can leverage the durable task scheduler, feel free to reach out to us anytime. Our line is always open - DurableTaskScheduler@microsoft.com.3.9KViews1like3Comments