User Authentication and Authorisation
- Microsoft identity platform enables users to authenticate with Microsoft identities or social accounts
- Components of Microsoft identity platform:
- OAuth 2.0 and OpenID Connect: Authenticate with several identity types
- Open-source libraries: Microsoft Authentication Libraries (MSAL) and support for other standards-compliant libraries
- Microsoft identity platform endpoint: Integrates with the open-source libraries to implement human readable scopes compliant with industry standards
- Application management portal: Registration and configuration experience in Azure portal
- Application configuration API and PowerShell: Programmatic configuration of applications through Microsoft Graph API for automation
Service Principals
- Delegation of Identity and Access Management functions to Entra ID requires an application to be registered with an Entra tenant
- Create an identity configuration for your application to integrate with Entra ID
- Single-tenant: Only accessible in your tenant
- Multi-tenant: Accessible in other tenants
- Entra applications are scoped to only one application object, residing in the tenant where the application was registered
- Application objects are used as a template to create one or more service principal objects
- Application objects describe how the service can issue tokens for application access, resources the application might need to access and the actions it can take
- Entities requesting access to a resource secured by an Entra tenant must be represented by a security principal
- Applies to both users (user principal) and applications (service principal)
- Security principals define the access policy and permissions for the user/application in the Entra tenant, enabling features such as authentication and authorisation of resources
- Three types of service principal:
- Application:
- A global application object in a single tenant, each created service principal references this object
- The service principal object defines what the app can actually do in the specific tenant, who can access the app and what resources the app can access
- Managed identity:
- Represents managed identity, providing an identity for applications to use when connecting to resources supporting Entra authentication
- Service principals representing managed identities can be granted access and permissions but can't be updated or modified directly
- Legacy:
- Represents a legacy app, which is an app created before app registrations were introduced
- Legacy service principals can have credentials, service principal names, reply URLs and other properties that an authorised user can edit (no associated app registration)
- Application:
- Application objects vs. service principals:
- The application object is the global representation of your application for use across all tenants and the service principal is the local representation for use in a specific tenant
- The application object serves as the template from which common and default properties are derived for use in creating corresponding service principal objects
- Application objects have a one-to-one relationship with the application and one-to-many relationships with its corresponding service principal objects
- A service principal must be created in each tenant where the application is used to establish an identity for access to resources being secured by the tenant
- Single-tenant apps have one service principal, multi-tenant apps has a service principal in each tenant where a user from that tenant consented to its use
Permissions and Content
- Applications using the Microsoft identity platform follow an authorisation model, allowing users and admins to control data access
- Microsoft identity platform uses OAuth 2.0 for third-party app access to web-hosted resources on behalf of users
- Resources (ex. Microsoft Graph, Azure Key Vault) define permission sets to allow third-party apps to request only the permissions they need
- Permission sets in OAuth 2.0 are known as scopes, represented as string values in the Microsoft identity platform (ex. https://graph.microsoft.com/Calendars.Read for calendar read access)
- Apps typically request these permissions via the Microsoft identity authorize endpoint with specified scopes
- Some high-privilege permissions require administrator consent and must be granted via the administrator consent endpoint
- Microsoft identity platform supports two permission types:
- Delegated access permissions: For apps with a signed-in user; permissions are granted by the user or an admin, allowing the app to act on behalf of the user
- App-only access permissions: For apps without a signed-in user, such as background services; only an admin can grant these permissions
- Consent is required for applications on the Microsoft identity platform to access resources or APIs, there are three consent types:
- Static user consent:
- All permissions are specified in the app's Azure portal configuration
- User or admin is prompted for consent at first sign-in if permissions haven’t been granted
- Admins can consent on behalf of the entire organisation
- A drawback is it requires all permissions at initial sign-in, which can overwhelm users; limited flexibility for accessing unknown resources later
- Incremental and dynamic user consent:
- Apps request permissions incrementally based on user interactions, without needing all permissions upfront
- New scopes can be requested during runtime, only requiring user consent for new permissions
- Applies only to delegated permissions, not app-only access
- High-privilege permissions requiring admin consent must still be pre-registered in Azure to allow admins to consent for the organisation
- Admin consent:
- Required for high-privilege permissions to provide extra control for organisational data access
- Admin consent can be done on behalf of the entire organization if permissions are registered statically in the app
- Ensures reduced admin cycles by predefining necessary permissions in the app registration portal
- Static user consent:
- Requesting individual user consent:
- In OpenID Connect or OAuth 2.0 requests, apps request necessary permissions using the scope query parameter
- The scope parameter lists permissions (space-separated), each indicated by adding a permission value to the resource's identifier
- Example:
GET https://login.microsoftonline.com/common/oauth2/v2.0/authorize? client_id=00001111-aaaa-2222-bbbb-3333cccc4444 &response_type=code &redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F &response_mode=query &scope= https%3A%2F%2Fgraph.microsoft.com%2Fcalendars.read%20 https%3A%2F%2Fgraph.microsoft.com%2Fmail.send &state=12345
- Example request format includes parameters like client_id, response_type, redirect_uri, and scope with specific permissions
- If user consent for the requested permissions doesn’t exist and admin consent hasn’t been provided, Microsoft identity platform prompts the user for consent
Microsoft Authentical Library (MSAL)
- Enables developers to acquire tokens from the Microsoft Identity platform
- Authenticate users and access secured web APIs
- Benefits:
- Don't need to directly use OAuth libraries or code against the protocol in your app
- Acquires tokens on behalf of a user or application
- Maintains token cache and refreshes tokens for you when they're close to expiry
- Helps specify which audience can sign in
- Helps set up application from configuration files
- Helps troubleshoot app by exposing actional exceptions, logging and telemetry
- MSAL can be acquired from many application types, including web apps, web APIs, single-page apps, mobile / native apps, daemons and server-side apps
- Authentication flows:
- Authorisation code: User sign-in and access to web APIs on behalf of the user
- Client credentials: Access to web APIs by using the identity of the application itself
- Device code: User sign-in and access to web APIs on behalf of the user on input-constrained devices like smart TVs and IoT devices
- Implicit grant: User sign-in and access to web APIs on behalf of the user, not supported on desktop and mobile (use first option instead)
- On-behalf-of (OBO): Access from the upstream web API to a downstream web API on behalf of the user
- Username/password (ROPC): Allows an app to sign in the user by directly handling their password (not recommended)
- Integrated Windows authentication (IWA): Allows apps on domain or Microsoft Entra joined computers to acquire a token silently (without UI interaction from the user)
Public Client and Confidential Client Applications
- A client is a software entity that has a unique identifier assigned by an identity provider
- There are two types of clients, public and confidential clients
- They differ based on their ability to authenticate securely with the authorisation server
- Public client applications run on devices (desktop, browserless APIs, mobile or client-side browser apps)
- Can't be trusted to safely keep app secrets, they can only access web APIs on behalf of the user
- Source or compiled bytecode of an app can be read, disassembled or inspected by untrusted parties
- Can't have client secrets as they can't hold configuration time secrets
- Confidential client applications run on servers (web apps, web API apps or service/daemon apps)
- Considered difficult to access by users or attackers
- Can hold configuration-time secrets to assert proof of identity
- Client ID is exposed through the browser, but secret is only passed in the back channel and never directly exposed
Initialising Client Applications
- With MSAL.NET 3.x, recommended way to instantiate an app is using the app builders PublicClientApplicationBuilder amd ConfidentialClientApplicationBuilder
- Enable configuration of an app either from code, a configuration file or using both approaches
// Instantiates a public client application IPublicClientApplication app = PublicClientApplicationBuilder.Create(clientId).Build();
// Instantiate a confidential application, handling tokens from users in the Microsoft Azure public cloud string redirectUri = "https://myapp.azurewebsites.net"; IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId) .WithClientSecret(clientSecret) .WithRedirectUri(redirectUri ) .Build();
- Need to register the app before initialising it so it can be integrated with the Microsoft identity platform, some relevant information includes:
- Application (client) ID
- Directory (tenant) ID: Provides identity and access management capabilities to apps and resources used by your organisation
- Identity provider URL and the sign-in audience for your application (combined are known as the authority)
- Client credentials: Can take the form of an application secret (client secret string) or certificate (of type X509Certificate2) if it's a confidential client app
- For web apps, you need to set the Redirect URI where the identity provider will contact back your app with the security tokens
- Using .with methods, we can apply modifiers on the application builders
// .WithAuthority sets the app's default authority to a Microsoft Entra authority IPublicClientApplication app; app = PublicClientApplicationBuilder.Create(clientId) .WithAuthority(AzureCloudInstance.AzurePublic, tenantId) .Build();
// .WithRedirectUri overrides the default redirect URI IPublicClientApplication app; app = PublicClientApplicationBuilder.Create(client_id) .WithAuthority(AzureCloudInstance.AzurePublic, tenant_id) .WithRedirectUri("http://localhost") .Build();
- Some common modifiers to set on a public or confidential client (some methods are exclusive to either such as .WithCertificate(X509Certificate2 certifcate) and .WithClientSecret(string clientSecret)):
- .WithAuthority: Sets the application default authority to a Microsoft Entra authority, with the possibility of choosing the Azure Cloud, the audience, the tenant (tenant ID or domain name) or providing directly the authority URI
- .WithTenant(string tenantId): Overrides the tenant ID or the tenant description
- .WithClientId(string): Overrides the client ID
- .WithRedirectUri(string redirectUri): Overrides default redirect URI
- .WithDebugLoggingCallback(): If called, app calls Debug.Write to enable debugging traces
- .WithLogging(): If called, app calls a callback with debugging traces
- .WithTelemetry(TelemetryCallback telemetryCallback): Sets delegate used to send telemetry
Shared Access Signatures
- A signed URI pointing to one or more storage resources and includes a token that contains a special set of query parameters
- Signature is generated using SAS parameters and signed with a specific key for authorisation
- Types of SAS:
- User Delegation SAS:
- Secured with Entra credentials
- Exclusive to Blob storage
- Service SAS:
- Secured with the storage account key
- Grants access to Blob, Queue, Table or Azure Files
- Account SAS:
- Secured with the storage account key
- Grants access to multiple storage services and supports all operations of service/user delegation SAS
- User Delegation SAS:
- Components required to access data with SAS: Resource URI and SAS token
- Example URI: https://medicalrecords.blob.core.windows.net/patient-images/patient-116139-nq8z7f.jpg?
- Example SAS token: sp=r&st=2020-01-20T11:42:32Z&se=2020-01-20T19:42:32Z&spr=https&sv=2019-02-02&sr=b&sig=SrW1HZ5Nb6MbRzTbXCaPm%2BJiSEn15tC91Y4umMPwVZs%3D
- SAS token composition includes parameters defining permissions, start/end times, protocols, version and signature for authorisation
- SAS token components:
- sp: Access rights (ex. r for read, a for add, c for create, d for delete, l for list, w for write). Example: sp=r allows read-only access; sp=acdlrw grants all rights
- st: Start time for access in UTC. Example: st=2020-01-20T11:42:32Z
- se: Expiry time for access in UTC. Example: se=2020-01-20T19:42:32Z (8 hours)
- sv: API version for Azure Storage. Example: sv=2019-02-02
- sr: Resource type (b for blob, q for queue, t for table, etc.). Example: sr=b
- sig: Cryptographic signature ensuring the token's authenticity
- Best practices:
- Use HTTPS
- Prefer User Delegation SAS (removes need to storage account keys in code or use Entra ID for credential management)
- Set minimal expiration time
- Grant minimal required privileges
- Avoid SAS where necessary (for high-risk scenarios, use middle-tier services instead of SAS)
Choose When to Use SAS
- Use SAS when you need to:
- Provide secure access to storage resources for clients without direct permissions
- Provide read/write permissions to users for their own data in a storage account
- Design patterns for SAS usage:
- Front-End Proxy Service:
- Clients interact with a proxy service for authentication and data validation
- Suitable for enforcing business rules but may be expensive for large data or high transaction volumes
- SAS Provider Service:
- Lightweight service authenticates clients and generates SAS
- Clients access storage resources directly using SAS, reducing load on the front-end proxy
- Front-End Proxy Service:
- The hybrid approach combines both previous design patterns:
- Front-end proxy for data requiring processing/validation
- SAS for direct access to less sensitive data
- Copy operations:
- Required for source authorisation when copying between different storage accounts or resource types
- Examples:
- Blob-to-blob or file-to-file in different accounts
- Blob-to-file or file-to-blob, even within the same account
Stored Access Policies
- Provides server-side control over service-level SAS
- Groups SAS and enforces additional restrictions
- Allows modification of start time, expiry time, permissions or revocation of SAS after issuance
- Supported storage resources: Blob containers, file shares, queues and tables
- Access policy for SAS consists of start time, expiry time and permissions
- Parameters can be fully on the SAS URI, within the stored access policy or split between the two (but not duplicated)
- Creating/Modifying a stored access policy:
- Use Set ACL operation for the resource (ex. Set Container ACL, Set Queue ACL, Set Table ACL, Set Share ACL)
- Request body requirements:
- Signed Identifier: A unique name up to 64 characters
- Optional Access Policy Parameters: Start time, expiry time and permissions
BlobSignedIdentifier identifier = new BlobSignedIdentifier { Id = "stored access policy identifier", AccessPolicy = new BlobAccessPolicy { ExpiresOn = DateTimeOffset.UtcNow.AddHours(1), Permissions = "rw" } }; blobContainer.SetAccessPolicy(permissions: new BlobSignedIdentifier[] { identifier });
az storage container policy create \ --name <stored access policy identifier> \ --container-name <container name> \ --start <start time UTC datetime> \ --expiry <expiry time UTC datetime> \ --permissions <(a)dd, (c)reate, (d)elete, (l)ist, (r)ead, or (w)rite> \ --account-key <storage account key> \ --account-name <storage account name> \
- Modify a stored access policy:
- Use the Set ACL operation to replace the existing policy
- Ex. change permissions from read/write to read-only for future requests
- Revoking a stored cccess policy:
- Options:
- Delete the policy: Removes access immediately
- Rename the policy: Change the signed identifier to break associations with existing SAS
- Set expiry time to the past: Invalidates associated SAS immediately
- Options:
- Removing access policies:
- Single policy: Use Set ACL with the signed identifiers to retain
- All policies: Use Set ACL with an empty request body to clear all policies
- Immediate effects: Any deletion, renaming or modifications instantly affects all SAS linked to the policy
Microsoft Graph
- Provides a unified programmability model for accessing and leveraging Microsoft 365 data
- Main components:
- Microsoft Graph API:
- Single endpoint: https://graph.microsoft.com
- Access via REST APIs or SDKs
- Manages user and device identity, access, compliance, security and protects against data loss
- Microsoft Graph Connectors:
- Integrate external data (ex. Box, Google Drive, Jira, Salesforce) into Microsoft Graph services
- Enhance experiences like Microsoft Search
- Microsoft Graph Data Connect:
- Tools for secure, scalable delivery of Microsoft Graph data to Azure data stores
- Enables building intelligent applications with Azure development tools
- Microsoft Graph API:
Query Microsoft Graph by Using REST
- Microsoft Graph API overview:
- A RESTful web API for accessing Microsoft Cloud service resources
- Requires app registration and authentication tokens (user/service-based)
- Most resources, methods and enumerations are defined in the microsoft.graph namespace (ex. call records API in microsoft.graph.callRecords)
- REST API request structure:
Components:
- HTTP Method: Specifies the operation (ex. GET, POST)
- Version: API version in use (ex. v1.0, beta)
- Resource: Target resource (ex. user, email)
- Query Parameters: Optional OData query or REST method parameters for customisation
{HTTP method} https://graph.microsoft.com/{version}/{resource}?{query-parameters}
- REST API Response:
- Status Code: HTTP code indicating success or failure
- Response Message: Requested data or operation result (can be empty)
- nextLink: URL for paginated data in @odata.nextLink for large result sets
- HTTP Methods in Microsoft Graph:
- GET: Reads data from a resource (no request body needed)
- POST: Creates a new resource or performs an action (requires JSON request body)
- PATCH: Updates a resource with new values (requires JSON request body)
- PUT: Replaces a resource with a new one (requires JSON request body)
- DELETE: Removes a resource (no request body needed)
- API Versions:
- v1.0:
- Stable and generally available APIs
- Recommended for production apps
- beta:
- Preview APIs that may change
- Use only for testing apps in development, not for production
- v1.0:
- Resources:
- Represents an entity or complex type, often defined with properties
- Entities: Always include an id property
- Examples of resources: me, user, group, drive, site
- Relationships: Access related resources (ex. me/messages, me/drive)
- Methods: Perform actions on resources (ex. me/sendMail to send an email)
- Permissions:
- Different resources and methods require specific permissions
- Higher permissions are typically needed for creating or updating resources compared to reading them
- Query Parameters:
- Customise responses using OData system query options or other accepted parameters
- Common uses:
- Include/exclude specific properties in the response
- Filter results based on specific conditions
GET https://graph.microsoft.com/v1.0/me/messages?filter=emailAddress eq 'jon@contoso.com'
Query Microsoft Graph by Using SDKs
- Simplify building efficient and resilient applications accessing Microsoft Graph
- Two main components:
- Service Library:
- Generated from Microsoft Graph metadata
- Provides models and request builders for a rich, discoverable experience
- Core Library:
- Enhances functionality across Microsoft Graph services with features like:
- Retry handling
- Secure redirects
- Transparent authentication
- Payload compression
- Supports tasks such as:
- Paging through collections
- Creating batch requests
- Enhances functionality across Microsoft Graph services with features like:
- Service Library:
- Microsoft Graph .NET SDK Packages:
- Install the required NuGet packages based on your API version needs (v1.0 or beta)
- Microsoft.Graph:
- Accesses the v1.0 endpoint with a fluent API
- Depends on Microsoft.Graph.Core
- Microsoft.Graph.Beta:
- Accesses the beta endpoint with a fluent API
- Depends on Microsoft.Graph.Core
- Microsoft.Graph.Core:
- Core library for making calls to Microsoft Graph
- Creating a Microsoft Graph client:
- Choose an authentication provider and create the client instance
var scopes = new[] { "User.Read" }; // Multi-tenant apps can use "common", // single-tenant apps must use the tenant ID from the Azure portal var tenantId = "common"; // Value from app registration var clientId = "YOUR_CLIENT_ID"; // using Azure.Identity; var options = new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud }; // Callback function that receives the user prompt // Prompt contains the generated device code that you must // enter during the auth process in the browser Func<DeviceCodeInfo, CancellationToken, Task> callback = (code, cancellation) => { Console.WriteLine(code.Message); return Task.FromResult(0); }; // /dotnet/api/azure.identity.devicecodecredential var deviceCodeCredential = new DeviceCodeCredential( callback, tenantId, clientId, options); var graphClient = new GraphServiceClient(deviceCodeCredential, scopes);
- To read information from Microsoft Graph, create a request object and run the GET method on the request
// GET https://graph.microsoft.com/v1.0/me var user = await graphClient.Me .GetAsync();
- Retrieving a list of entities:
- Similar to retrieving a single entity but with additional request configuration options
- $filter: This query parameter reduces result set to rows matching a specific condition
- $orderBy: This query parameter sorts list of entities by specified properties
// GET https://graph.microsoft.com/v1.0/me/messages?$select=subject,sender&$filter=<some condition>&orderBy=receivedDateTime var messages = await graphClient.Me.Messages .Request() .Select(m => new { m.Subject, m.Sender }) .Filter("<filter condition>") .OrderBy("receivedDateTime") .GetAsync();
- Delete an entity involves constructing a request similar to retrieving an entity but use the DELETE request instead of GET
// DELETE https://graph.microsoft.com/v1.0/me/messages/{message-id} string messageId = "AQMkAGUy..."; var message = await graphClient.Me.Messages[messageId] .Request() .DeleteAsync();
- Create a new entity:
- New SDKs supporting a fluent style can use the Add method to add new items to collections
- For template-based SDKs, the request object exposes a post method
// POST https://graph.microsoft.com/v1.0/me/calendars var calendar = new Calendar { Name = "Volunteer" }; var newCalendar = await graphClient.Me.Calendars .Request() .AddAsync(calendar);
Apply Best Practices to Microsoft Graph
- Authentication:
- Use OAuth 2.0 access tokens via:
- HTTP Authorization header as a Bearer token
- Graph client constructor with a Microsoft Graph client library
- Use Microsoft Authentication Library (MSAL) to acquire tokens
- Use OAuth 2.0 access tokens via:
- Consent and authorisation:
- Least privilege
- Permission types:
- Delegated permissions for interactive apps with signed-in users
- Application permissions for background services without users (avoid for interactive scenarios)
- End-user/admin experience:
- Match permission requests to who consents (end-user vs. admin)
- Understand static, dynamic and incremental consent flows
- Multi-Tenant Applications:
- Handle varying tenant settings (ex. admin-only consent or custom policies)
- Expect and manage 403 errors for restricted actions
- Handling responses effectively:
- Pagination:
- Resource collections may return results in multiple pages due to server-side limits
- Use the odata.nextLink property to fetch subsequent pages until no @odata.nextLink is present
- Evolvable enumerations:
- Microsoft Graph can add new members to enums without breaking existing applications
- By default, GET requests return only known members
- To handle unknown members, use the Prefer HTTP request header to opt in for receiving them
- Pagination:
- Storing data locally:
- Prefer making real-time calls to Microsoft Graph when possible
- Cache/store data locally only for specific justified scenarios, ensure compliance with terms of use, privacy policy and Microsoft APIs Term of Use
- Implement appropriate data retention and deletion mechanisms to manage stored data responsibly