Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
154090 stories
·
33 followers

Token Issuer Isolation: Why It Matters for Security and Compliance

1 Share

In a shared-issuer model, every tenant's tokens are signed with the same cryptographic key. One compromised key compromises everyone. Every tenant. Every token. Every session. In regulated industries like financial services, healthcare, government, and enterprise SaaS, this is not a theoretical risk. It is a finding on an audit report, a red flag in a SOC 2 examination, and a deal-breaker in a procurement security review.

Token issuer isolation changes the equation. Each tenant gets its own signing keys, JWKS endpoint, and token validation chain. A key compromise in one tenant stays contained to that tenant. In other words, token issuer isolation is the control that determines whether a key compromise is a catastrophic breach or a contained incident.

This post explains what issuer isolation means in cryptographic terms, why it matters for SOC 2 and ISO 27001, and how Duende IdentityServer 8 implements it without adding to your operational burden.

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

How to upload files in an ASP.NET Core Web API

1 Share

This blog post is originally published on https://blog.elmah.io/how-to-upload-files-in-an-asp-net-core-web-api/

Files are an integrated part of an application. From a social app to an ERP, some form of media exists in the ecosystem. .NET Core APIs provide built-in support for uploading and fetching documents. In today's post, I will show you how to cleanly and efficiently use .NET Core's file system classes to receive file uploads.

How to upload files in an ASP.NET Core Web API

Uploading files in ASP.NET Core API

I am creating an API with .NET 10 to accomplish our goal.

Step 1: Create a project

dotnet new webapi -n fileUploadDemo
cd fileUploadDemo

Step 2: Create a controller

using Microsoft.AspNetCore.Mvc;

namespace fileUploadDemo.Controllers;

[ApiController]
[Route("api/[controller]")]
public class FileController: ControllerBase
{
    [HttpPost("upload")]
    public async Task<IActionResult> Upload(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest("No file uploaded.");
    
        var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");
    
        if (!Directory.Exists(uploadsFolder))
            Directory.CreateDirectory(uploadsFolder);
    
        var filePath = Path.Combine(uploadsFolder, file.FileName);
    
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await file.CopyToAsync(stream);
        }
    
        return Ok(new { file.FileName, file.Length });
    }
}

The endpoint upload will save the file to the "Uploads" directory and return the filename and length.

Step 3: Configure Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

var app = builder.Build();

app.UseStaticFiles();
app.MapControllers();

app.UseHttpsRedirection();

app.Run();

app.UseStaticFiles(); enables the app to serve static files directly over HTTP

Make sure your Uploads folder is accessible.

Step 4: Run and test

dotnet run
Test

Hence, our file is saved:

Solution explorer

So far, we have seen a naive, minimal approach to saving a file in an ASP.NET Core API. However, in real applications, you may split it up into layers, services, or however you prefer to structure applications.

Adding OpenApi/Swagger documentation

.NET 10 does not create OpenAPI (formerly Swagger) documentation like previous versions. So we have to add them. First, install the NuGet package:

dotnet add package Swashbuckle.AspNetCore

Next, change Program.cs:

using Microsoft.AspNetCore.Http.Features;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Upload V1");
});

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseStaticFiles();
app.MapControllers();
app.UseHttpsRedirection();

app.Run();

The AddOpenApi() method registers with the OpenAPI document generation services. Then AddEndpointsApiExplorer() adds an API explorer service that discovers endpoints and provides metadata about routes, parameters, and return types. Without this, Swagger would show no APIs. AddSwaggerGen() configures Swashbuckle (Swagger for .NET) and generates a Swagger document. Besides, it adds schema generation for your DTOs. UseSwagger() registers middleware to serve the Swagger JSON document. You must register middleware before using SwaggerUI. The UseSwaggerUI configures the Swagger UI web interface and navigates the UI to the JSON document. It basically adds UI elements, such as the page and Try out buttons.

Result

Swagger
Swagger
Result

Restrict the file size limit

It is recommended to set a maximum file size limit for uploaded files:

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = 104857600; 
});

With the property MultipartBodyLengthLimit, I set the maximum size of the multipart body to 100 MBs.

Allowing formats for different file types

Each document type has a set of extensions: images are usually in JPG or PNG, and documents are usually in PDF or docx. For different purposes, we should validate the input. Like for a profile picture, users may not upload a docx or a PDF, while for a report, they should avoid JPG. To enforce it in our API, I will separate the image and document endpoints.

UploadImage specified for uploading image files:

[HttpPost("UploadImage")]
public async Task<IActionResult> UploadImageAsync(IFormFile file)
{
    if (file == null || file.Length == 0)
        return BadRequest("No file uploaded.");

    var allowedExtensions = new[] { ".jpg", ".png" };
    var extension = Path.GetExtension(file.FileName);
    
    if (!allowedExtensions.Contains(extension))
    {
        var extensionsWithoutDots = allowedExtensions.Select(ext => ext.TrimStart('.'));
        return BadRequest($"Invalid file type. Allowed types are: {string.Join(", ", extensionsWithoutDots)}");
    }
    
    var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");

    if (!Directory.Exists(uploadsFolder))
        Directory.CreateDirectory(uploadsFolder);

    var filePath = Path.Combine(uploadsFolder, file.FileName);

    using (var stream = new FileStream(filePath, FileMode.Create))
    {
        await file.CopyToAsync(stream);
    }

    return Ok(new { file.FileName, file.Length });
}

UploadDocument method for uploading document files:


    [HttpPost("UploadDocument")]
    public async Task<IActionResult> UploadDocumentAsync(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest("No file uploaded.");

        var allowedExtensions = new[] { ".pdf", ".txt" };
        var extension = Path.GetExtension(file.FileName);
        
        if (!allowedExtensions.Contains(extension))
        {
            var extensionsWithoutDots = allowedExtensions.Select(ext => ext.TrimStart('.'));
            return BadRequest($"Invalid file type. Allowed types are: {string.Join(", ", extensionsWithoutDots)}");
        }
        
        var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");
    
        if (!Directory.Exists(uploadsFolder))
            Directory.CreateDirectory(uploadsFolder);
    
        var filePath = Path.Combine(uploadsFolder, file.FileName);
    
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await file.CopyToAsync(stream);
        }
    
        return Ok(new { file.FileName, file.Length });
    }

The prior method only supports JPG and PNG, while the latter supports PDF and TXT. Lets test it from SwaggerUI:

API
API

On a correct input:

Upload png file
Result

If we inspect the Uploads folder, we can see the uploaded file:

Result

While the other method:

Upload invalid file
Bad request

And for the allowed document:

Upload valid document
Upload result
Result

Renaming the file to standard naming

We have now successfully uploaded files. However, there is a problem: you may notice that file names are arbitrary and random.

Uploads

For real applications, we often want to control the names of the files to avoid someone uploading a Snapchat image of themselves with a weird-looking filename. Let's fix that.

Step 1: Add model

First, introduce a model to input images. It will contain a type specifying what the file is for.

Add File type enum:

namespace fileUploadDemo.Models.Enums;

public enum FileTypeEnum
{
    ProfilePicture = 1,
    PostImage = 2,
    Resume = 3
}

File input:

using fileUploadDemo.Models.Enums;

namespace fileUploadDemo.Models.Dtos;

public class FileDtoInp
{
    public FileTypeEnum Type { get; set; }
    public IFormFile File { get; set; }
}

To return, we will use a model instead of an anonymous object:

namespace fileUploadDemo.Models.Dtos;

public class FileDto
{
    public string FileName { get; set; } = string.Empty;
    public long Length { get; set; } 
}

Step 2: Add service layer

So far, I have added everything to the controller, which is a bad practice. I'll add a service layer, but this can be implemented in whatever way you prefer.

Add a IFileService interface:

using fileUploadDemo.Models.Dtos;

namespace fileUploadDemo.Services.IServices;

public interface IFileService
{
    Task<FileDto> UploadImageAsync(FileDtoInp input);
    Task<FileDto> UploadDocumentAsync(FileDtoInp input);
}

And a FileService implementation:

using fileUploadDemo.Models.Dtos;
using fileUploadDemo.Models.Enums;
using fileUploadDemo.Services.IServices;

namespace fileUploadDemo.Services;

public class FileService: IFileService
{
    private readonly string _uploadsFolder;

    public FileService(IWebHostEnvironment env)
    {
        _uploadsFolder = Path.Combine(env.ContentRootPath, "Uploads");
    }
    public async Task<FileDto> UploadImageAsync(FileDtoInp input)
    {
        if (input.File == null || input.File.Length == 0)
            throw new Exception("No file uploaded.");
        
        var allowedExtensions = new[] { ".jpg", ".png" };
        var extension = Path.GetExtension(input.File.FileName);
        
        if (!allowedExtensions.Contains(extension))
        {
            var extensionsWithoutDots = allowedExtensions.Select(ext => ext.TrimStart('.'));
            throw new Exception($"Invalid file type. Allowed types are: {string.Join(", ", extensionsWithoutDots)}");
        }
        
        if (!Directory.Exists(_uploadsFolder))
            Directory.CreateDirectory(_uploadsFolder);

        var fileName = $"{ GetFileName(input.Type) }{extension}";
        
        var filePath = Path.Combine(_uploadsFolder, fileName);
    
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await input.File.CopyToAsync(stream);
        }

        return new 
            FileDto()
            {
                FileName = fileName, 
                Length = input.File.Length
            };
    }

    public async Task<FileDto> UploadDocumentAsync(FileDtoInp input)
    {
        if (input.File == null || input.File.Length == 0)
            throw new Exception("No file uploaded.");

        var allowedExtensions = new[] { ".pdf", ".txt" };
        var extension = Path.GetExtension(input.File.FileName);
        
        if (!allowedExtensions.Contains(extension))
        {
            var extensionsWithoutDots = allowedExtensions.Select(ext => ext.TrimStart('.'));
            throw new Exception($"Invalid file type. Allowed types are: {string.Join(", ", extensionsWithoutDots)}");
        }
        
        if (!Directory.Exists(_uploadsFolder))
            Directory.CreateDirectory(_uploadsFolder);
    
        var fileName = $"{ GetFileName(input.Type) }{extension}";
        var filePath = Path.Combine(_uploadsFolder, fileName);
    
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await input.File.CopyToAsync(stream);
        }

        return new 
            FileDto()
            {
                FileName = fileName, 
                Length = input.File.Length
            };
    }
    
    private string GetFileName(FileTypeEnum input)
        => input switch
        {
            FileTypeEnum.ProfilePicture => $"PRF-{DateTime.UtcNow:yyMMddHHmmss}",
            FileTypeEnum.PostImage => $"PST-{DateTime.UtcNow:yyMMddHHmmss}",
            FileTypeEnum.Resume => $"RSM-{DateTime.UtcNow:yyMMddHHmmss}",
            _ => throw new ArgumentOutOfRangeException(nameof(input), input, null)
        };
}

Let's understand what is happening here. We have methods for uploading images and documents, which first validate the file types. They actually abstracted the logic from the earlier upgrade of our application from controllers to services, which is always recommended. The main update here is the GetFileName method that returns a standardized name for each type. To make it collision-proof, I concatenated the current datetime into the name. Besides, the upload directory address is initialized in the constructor and used throughout the service. An even better way is to use it in appsettings.json and use the option pattern. Lets keep it simple for now. At last, I returned the FileDto model as output. You may also want to switch to not using exceptions for input validation, but I will leave that part up to you.

Step 3: Register the new service

builder.Services.AddScoped<IFileService, FileService>();

In Program.csI have registered the service dependency.

Step 4: Update controller

Now, the controller will inject and use the IFileService we just created:

using fileUploadDemo.Models.Dtos;
using fileUploadDemo.Services.IServices;
using Microsoft.AspNetCore.Mvc;

namespace fileUploadDemo.Controllers;

[ApiController]
[Route("api/[controller]")]
public class FileController: ControllerBase
{
    private readonly IFileService _fileService;

    public FileController(IFileService fileService)
    {
        _fileService = fileService;
    }
    
    [HttpPost("UploadImage")]
    public async Task<IActionResult> UploadImageAsync([FromForm] FileDtoInp input)
    {
        var result = await _fileService.UploadImageAsync(input);
        return Ok(result);
    }
    
    [HttpPost("UploadDocument")]
    public async Task<IActionResult> UploadDocumentAsync([FromForm] FileDtoInp input)
    {
        var result = await _fileService.UploadDocumentAsync(input);
        return Ok(result);
    }
}

[FromForm] specifies the input as form-data, not a JSON body, ensuring the endpoint receives files.

Step 4: Run and test

dotnet run
API
Result

Ensure the file is named according to our standard, regardless of its original name.

The same happens with the documents:

API
Result
Uploaded files

Apart from naming files with date and time, using a unique GUID or the username is also a common practice. Consider the following ways:

var fileName = $"{ Guid.NewGuid().ToString() }{extension}";

or:

var fileName = $"{ _userContext.Username }{extension}";

Creating a GET endpoint to fetch a file

We have covered several ways to upload a file in an ASP.NET Core API. Next requirement naturally arises: how to fetch the saved file? Let's design an endpoint for it.

A new model to return the file from the service to the controller could be implemented like this:

namespace fileUploadDemo.Models.Dtos;

public class FileResultDto
{
    public byte[] Content { get; set; } = default!;
    public string ContentType { get; set; } = string.Empty;
    public string FileName { get; set; } = string.Empty;
}

Our service implementation should include a new method for fetching a file:

public async Task<FileResultDto?> GetFileAsync(string key)
{
    if (string.IsNullOrWhiteSpace(key) || key.Contains(".."))
        return null;

    var filePath = Path.Combine(_uploadsFolder, key);

    if (!System.IO.File.Exists(filePath))
        return null;

    var extension = Path.GetExtension(filePath).ToLower();

    var contentType = extension switch
    {
        ".jpg" or ".jpeg" => "image/jpeg",
        ".png" => "image/png",
        ".pdf" => "application/pdf",
        _ => "application/octet-stream"
    };

    var bytes = await System.IO.File.ReadAllBytesAsync(filePath);

    return new FileResultDto
    {
        Content = bytes,
        ContentType = contentType,
        FileName = key
    };
}

After some initial string checks and combining the filename with the directory, we check whether the file exists. In the latter part, we convert the file into a MIME type based on the extension. var bytes = await System.IO.File.ReadAllBytesAsync(filePath); loads the entire file into memory and returns byte[]. If large files are uploaded, consider streaming instead. Finally, it packs the content and metadata into the response model and returns it.

Use it in the controller:

[HttpGet("GetFile")]
public async Task<IActionResult> GetFileAsync([FromQuery] string key)
{
    var result = await _fileService.GetFileAsync(key);

    if (result == null)
        return NotFound("File not found");

    return File(result.Content, result.ContentType, result.FileName);
}

[FromQuery] will prompt for the filename as a key param and pass it to the service. Once the data is received, the endpoint will return the actual file.

Test

API
Result with a Download file link

Simply, the user can download the file.

Tips for using file uploading in ASP.NET Core

For production environments and scalable systems, consider the following aspects.

  • For the production environment, use a cloud service such as Azure Blob or AWS S3 to save files for better reliability and speed.
  • Use the path in appsettings for security and good design.
  • Organize upload paths into separate directories for document- and module-wise uploads. Even separating directories for users is also a common practice, such as "images/Profile", "images/Posts", "documents/Resumes" or "images/john/".
  • Store metadata in a database for referencing and persistence. Creating an image-and-document table with a Type key is a great way to reference files to corresponding entities, such as User and Building, via ImageId and DocumentId. The tables can store other important information, such as file size, creation date, uploader user, file name, etc.
  • If your system requires a very large file upload, use chunk uploading.
  • Use file streaming for large file instead of buffer that can result in high RAM usage.
  • Enable progress tracking via SignalR.

Conclusion

The long story comes to an end, and we learned how to handle files in ASP.NET Core APIs. We started with minimal code and applied best practices when using images and documents. By following these tips, you can design a file structure securely and efficiently in a production-grade system.



Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Pack your agentic stack in Slack

1 Share
Ryan welcomes Jaime DeLanghe, chief product officer at Slack, to chat about how they’re preparing to integrate everybody’s agents in their chat application.
Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Signing in to Microsoft Foundry from OpenClaw using Azure AD: a smoother way to bring your models in

1 Share

This post is a quick update to walk through the new flow. If you read the previous one, think of this as the easier path I wish I had the first time round. If you have not seen the original, you can find it here: Integrating Microsoft Foundry with OpenClaw: Step by Step Model Configuration | Microsoft Community Hub

Pre-requisite:

You will need the Azure CLI (azure-cli) installed on your machine. The official install guide for Linux is here: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?view=azure-cli-latest

I am on Linux so I went the Homebrew route, which keeps things simple. The formula is here: https://formulae.brew.sh/formula/azure-cli 

Microsoft also has official docs covering the Homebrew/Linuxbrew install: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-macos?view=azure-cli-latest#install-with-homebrew

 

Once Homebrew is ready, run this in your terminal:

brew install azure-cli

 

 

Why this matters:

Before this update, every Foundry model you wanted to use in OpenClaw needed its own API key and endpoint pasted into the config. It worked, but it was tedious, and keys are easy to leak if you are copying them around. The Azure AD path solves both problems. You authenticate as yourself (or a service principal), OpenClaw asks Azure for the list of Foundry resources you have access to, and it brings the models in automatically.

Signing in to Microsoft Foundry from OpenClaw via Azure AD

A device-code OAuth handshake replaces the old static-API-key flow. OpenClaw delegates auth to the local Azure CLI; the CLI handles the browser-side sign-in, holds the resulting tokens, and refreshes them silently. OpenClaw then walks the Azure resource graph, subscriptions → Foundry resources → model deployments and registers each model into its own config. No API keys move through OpenClaw at any point.

Figure. Sequence diagram of the OAuth 2.0 device-authorization flow as orchestrated by OpenClaw. Phases 1–3 establish identity (the developer authenticates once, in a real browser, against Azure AD). Phases 4–5 perform service discovery (OpenClaw walks the ARM resource hierarchy, subscriptions → Foundry accounts → model deployments and persists the result to a local provider config). After registration, every model call OpenClaw makes against Foundry reuses the same Azure-CLI-managed token cache: tokens refresh transparently, and access is gated by the Foundry resource's RBAC assignments rather than a static API key. Dashed lines denote return values; the teal line in step 7 marks the single token-issuance event the rest of the system pivots on.

 

Walking through the new flow:

Start with the command to onboard openclaw as if you were setting up OpenClaw for the first time:

openclaw onboard

 

 

Kick things off with the OpenClaw onboard command, the same one you would use when setting up OpenClaw for the first time. When it prompts you, choose update values.

 

 

Next, you will be asked to configure your models. Scroll down a little and you will see Microsoft Foundry listed as a supported provider. Pick it.

From here, you have two options. You can sign in with an API key, which is what I covered in the previous blog post, or you can sign in through Azure AD. The Azure AD path is easier and more secure, so that is the one we will use.

 

 

OpenClaw will give you a URL and a device code. Copy the URL into your browser and use the code to complete the sign in. (This is where the az CLI from the pre-requisite section earns its keep.)

 

 

 

 

If everything worked, you should see a success prompt similar to this:

 

 

 

Once you are signed in, OpenClaw will ask you to pick the Azure subscription that your Microsoft Foundry resource lives in. Pick the subscription, then pick the Foundry resource where your models are deployed.

And that is pretty much it. All the models you have deployed to that Foundry resource get pulled into OpenClaw automatically. Compared to the old way of pasting API keys and endpoints one by one, this is a huge time saver, and you do not have to babysit any keys.

 

 

From here you can start using your Foundry-deployed models inside OpenClaw straight away:

 

 

 

Wrapping up

The Azure AD sign-in option in OpenClaw is one of those small updates that quietly removes a real pain point. If you have ever juggled multiple Foundry endpoints and rotated keys across them, you already know why. With this flow, you sign in once, your models show up, and you can get back to actually building.

If you have not tried OpenClaw with Microsoft Foundry yet, this is a good time to give it a go. And if you were holding off because of the key management overhead, that excuse is gone now.

 

References

Previous post on integrating Microsoft Foundry with OpenClaw using API keys: Integrating Microsoft Foundry with OpenClaw: Step by Step Model Configuration | Microsoft Community Hub 

Install the Azure CLI on Linux: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?view=azure-cli-latest 

Install the Azure CLI on macOS: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-macos?view=azure-cli-latest#install-with-homebrew 

Homebrew formula for azure-cli: https://formulae.brew.sh/formula/azure-cli 

Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete

How to Visualize Your Azure AI Workloads Usage for Observability

1 Share

This article assumes you already have an Azure Foundry project and resource deployed in Microsoft Foundry. The options referenced here are documented in detail in the linked articles; this post serves as a consolidated step by step guide bringing them all together and explaining where each option is most useful.

A Summary:

Need

Best Option

Quick day-over-day visual, minimal setup

Grafana Dashboard (Option 3)

Custom growth % calculations

App Insights + KQL in Log Analytics (Option 4)

Shareable, interactive report

Azure Workbooks (Option 5)

Per-user/per-agent granularity

APIM + App Insights (Option 6)

Quick one-off chart, export to Excel

Microsoft Foundry Monitor tab or App Insights Metrics Explorer (Option 1 and 2)

Option 1. Within the Microsoft Foundry Portal (Quickest, No Setup)

If you have models deployed in Microsoft Foundry and would like to monitor its usage, go to the New Foundry Portal → Build → Models → Monitor tab.

View metrics such as:

  • Estimated cost
  • Total token usage
  • Input vs. output tokens
  • Number of requests

This is the simplest way to monitor both model and agent usage.

Microsoft Foundry has a built in Monitor tab to view your model/agent usage.

For PAYG plans:

You can also view your total allocated quota (and figure out which Tier you are on) using the Quota Management Screen (New Foundry Portal → Operate → Quota tab). 

This screen shows how much your total allocated quota is, per model in a given subscription + region + Deployment Type (Global, Data Zones or Regional). For eg., in the image below, for gpt-4o, I am allocated 7M total TPM in my subscription. I am only using 150K TPM of the allocated 7M TPM amount.

Which means, my requests will get throttled if I exceed the 150K TPM limit. To avoid throttling, I would need to increase my shared allocation limit.

NOTE: you are charged for usage, so if you allow more capacity, you use more, so you pay more. 

 

Option 2: Azure Monitor Metrics Explorer

This is already built into the Azure Portal and gives you time-series charts out of the box.

  1. Go to Azure Portal → your Azure OpenAI / Foundry resource MonitoringMetrics
  2. Select a metric like AzureOpenAIRequests or TokenTransaction
  3. Set Aggregation to Sum (total) or Max and Time granularity to 1 day
  4. Split by ModelDeploymentName to see per-model trends
  5. Adjust the time range (e.g., last 30 days) — you'll see day-over-day bars/lines

Tip: You can pin these charts to an Azure Dashboard for a persistent view, or click Share → Download to Excel to get the raw data for your own analysis. 

Option 3: Azure Managed Grafana (Best Pre-Built Dashboard)

This is the best option for a polished, real-time, day-over-day dashboard with no custom code. There's a pre-built AI Foundry dashboard ready to import. [grafana.com], [Create a M...ed Grafana]

How to set it up:

  1. Create an Azure Managed Grafana workspace (if you don't have one)
  2. In Grafana, go to Dashboards → New → Import → enter dashboard ID 24039 (for Foundry)
  3. Select your Azure Monitor data source and point it to your Foundry resource
  4. Tip: You can also import this directly from the Azure Portal: Monitor → Dashboards with Grafana → AI Foundry.
  5. That's it — the dashboard gives you (per model deployment):

Token trends over time (inference, prompt, completion — day over day)

Request trends over time (AzureOpenAIRequests as a time series)

Latency trends (bonus)

NOTE: Default time range is 7 days — adjust to 30/60/90 days for growth trends

Option 4: Application Insights + KQL Queries (Most Flexible, Custom Reports)

If you want fully custom day-over-day growth calculations (e.g., % change day-to-day), this is the way. [azurefeeds.com]

Setup:

  1. Ensure your Foundry project is connected to an Application Insights resource (Foundry → Settings → Connected Resources). 
  2. Open up App Insights resource → Logs → New Query or choose a sample query. In the images below, we simply ran 'requests' and set the time range to 24 hours. 
  3. There is also a Kusto Query Language (KQL) mode or Simple mode on the right-hand side: 
    • Simple mode will let you run out of the box samples.
    • KQL mode will open up a query window for you to enter custom queries.
  4. Below are the results in grid view. 

Same view but showing a chart: 

Export options: 

Another way to get the above graphs are via Log Analytics. Simply enable Diagnostic Settings on your Azure OpenAI resource → send to a Log Analytics workspace. Open Log Analytics → Logs and try our your sample queries. 

Sample KQL for day-over-day token usage (adjust to your needs):

AzureMetrics | where MetricName in ("TokenTransaction", "ProcessedPromptTokens", "GeneratedTokens") | where TimeGenerated > ago(30d) | summarize DailyTokens = sum(Total) by bin(TimeGenerated, 1d), MetricName | order by TimeGenerated asc | render timechart

Result: 

Sample KQL for day-over-day growth % (adjust to your needs):

AzureMetrics | where MetricName == "TokenTransaction" | where TimeGenerated > ago(30d) | summarize DailyTokens = sum(Total) by Day = bin(TimeGenerated, 1d) | sort by Day asc | extend PrevDay = prev(DailyTokens) | extend GrowthPct = round((DailyTokens - PrevDay) / PrevDay * 100, 2) | project Day, DailyTokens, GrowthPct

Option 5: Azure Monitor Workbooks (Custom Dashboards, Shareable)

Workbooks let you build interactive, parameterized dashboards that combine metrics and KQL logs. 

What's more, you can select resources from multiple subscriptions and visualize them all in one place using Workbooks!

  1. Go to Azure Portal → Monitor → Workbooks → New
  2. Add a Metrics query panel → select your Log Analytics or App Insights or Foundry resource -> Enter the same query you used in Option 4.
  3. Do a test run and view the graphs (this can be viewed as charts or a list (grid view)):
You can select different resources (or subscriptions) and view them all in one pane.

      4. Save and share with your team.

Option 6: APIM + Application Insights (Granular Per-Caller/Per-Agent Tracking)

1. If your app routes requests through Azure API Management, you can use the azure-openai-emit-token-metric policy to send per-request token metrics to Application Insights with custom dimensions (User ID, Subscription ID, Agent, etc.). [Azure API...osoft Docs]

This is ideal for scenarios like:

  • "Which agent consumed the most tokens last week?"
  • "What's the token usage per API consumer/team?"

NOTE: Microsoft Foundry resources do not track usage by users. So, fronting your Foundry resource with an APIM could be a way to track users provided you pass the username/id in the request context. How you implement this is upto your app design. 

Ref: AI-Gateway/labs/token-metrics-emitting/token-metrics-emitting.ipynb at main · Azure-Samples/AI-Gateway · GitHub

Bonus: Check out all other APIM + AI related policies here:

AI-Gateway/labs/semantic-caching at main · Azure-Samples/AI-Gateway

AI-Gateway/labs/token-rate-limiting at main · Azure-Samples/AI-Gateway

Ref: AI-Gateway/labs/token-metrics-emitting/token-metrics-emitting.ipynb at main · Azure-Samples/AI-Gateway · GitHub
Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete

9 Codex Tips from the Codex Team

1 Share
From: AIDailyBrief
Duration: 25:29
Views: 732

Composer 2.5 narrows the gap with frontier coding models on key benchmarks while Cursor touts dramatic token‑efficiency at a fraction of the cost. Enterprise strategy is shifting toward harness‑first platforms and agent orchestration, capturing long‑running context, persistent memories, and post‑training improvements to rival model labs. Security research around Mythos Preview shows models synthesizing and refining exploit chains into functional proofs, prompting Cloudflare warnings about a novel class of model‑generated risk as companies operationalize agent tooling.

The AI Daily Brief helps you understand the most important news and discussions in AI.
Subscribe to the podcast version of The AI Daily Brief wherever you listen: https://pod.link/1680633614
Get it ad free at http://patreon.com/aidailybrief
Learn more about the show https://aidailybrief.ai/

Read the whole story
alvinashcraft
9 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories