Section 14

Security, IAM & cryptography

Who can access what (IAM), how data is kept secret and tamper-proof (cryptography, TLS, keys), and the mindset (Zero Trust, OWASP) that ties it together.

14.1 Authentication vs Authorization Added

User / service
→
Authentication"Who are you?" password · MFA · cert
→
Authorization"What can you do?" roles · scopes · policies
→
Resource
  • Authentication always comes first. Failure → 401. Authorization failure → 403.
  • Think AAA: Authentication, Authorization, Accounting (auditing).
How to implement it
Library / APIPackageUse it for
Authentication handlersMicrosoft.AspNetCore.Authentication.JwtBearer, .OpenIdConnect, .Certificate; cookies are built-inAuthN: validate a bearer token, an OIDC login, a client certificate or a cookie, and build the ClaimsPrincipal
Authorization policiesbuilt-in AddAuthorizationBuilder, RequireAuthorization, [Authorize]AuthZ: named rules such as RequireRole, RequireClaim or custom requirements
Resource-based checksbuilt-in IAuthorizationService + AuthorizationHandler<TReq, TResource>"Can this user edit this order?" Decided after you load the record
Auditing (accounting)built-in ILogger / OpenTelemetryLog who did what, with the user id from the claims
builder.Services.AddAuthentication().AddJwtBearer(); // settings from "Authentication:Schemes:Bearer"
builder.Services.AddAuthorizationBuilder()
    .AddPolicy("orders.write", p => p.RequireRole("Orders.Writer"))
    .AddPolicy("finance", p => p.RequireClaim("department", "Finance"));

var app = builder.Build();
app.UseAuthentication();   // who are you?  no or bad token -> 401 (challenge)
app.UseAuthorization();    // what can you do?  policy fails -> 403 (forbid)

app.MapPost("/orders", CreateOrder).RequireAuthorization("orders.write");

14.2 IAM: Identity & Access Management

The policies, processes and tools that make sure the right identities get the right access to the right resources, at the right time, for the right reasons.

User / app
→login
Identity provider (IdP)Entra ID · Okta · Ping
→token (claims, roles)
Applications / APIs / cloudenforce access

👤 Identities

  • Human: employees, customers (CIAM), partners (B2B)
  • Non-human: service principals, managed identities, workload identities, API clients

🔑 Authentication

  • Passwords + MFA
  • Passwordless: FIDO2, passkeys, Windows Hello
  • Certificates, federated tokens
  • Conditional access (device, location, risk)

🛂 Authorization models

  • RBAC: roles (Reader, Contributor)
  • ABAC: attributes (dept=Finance AND region=AU)
  • PBAC: central policy engine (OPA)
  • ReBAC: relationships (owner of the doc)
  • ACLs on resources

🔄 Lifecycle & governance (IGA)

  • Joiner-Mover-Leaver process
  • Automated provisioning (SCIM)
  • Access reviews / recertification
  • Segregation of duties (SoD)

👑 Privileged access (PAM / PIM)

  • Just-in-time elevation with approval
  • Time-bound admin roles
  • Session recording, break-glass accounts
  • CyberArk, Entra PIM

🌐 Federation & SSO

  • One login, many apps
  • Trust between organisations' IdPs
  • SAML, OIDC, WS-Fed
  • Principle of least privilege: give the minimum access, for the minimum time.
  • Prefer managed identities over secrets for service-to-service access in the cloud.
  • Leavers must be de-provisioned immediately. Orphaned accounts are a top audit finding.
  • Cloud IAM scope hierarchy (Azure): Management group → Subscription → Resource group → Resource. Assign roles at the lowest scope that works.
How to implement it
Library / APIPackageUse it for
Microsoft.Identity.WebMicrosoft.Identity.WebSign users in to web apps and protect APIs with Entra ID; call downstream APIs on the user's behalf
MSALMicrosoft.Identity.ClientGet tokens in desktop, mobile, console and daemon apps
Azure.IdentityAzure.IdentityNon-human identities with no secrets: DefaultAzureCredential, ManagedIdentityCredential, WorkloadIdentityCredential (AKS)
Microsoft GraphMicrosoft.GraphAutomate joiner-mover-leaver: users, groups, app role assignments
ASP.NET Core Identitybuilt-in Microsoft.AspNetCore.Identity.EntityFrameworkCoreYour own user store (CIAM-style): passwords, MFA, lockout, and passkeys (new in .NET 10)
Run your own IdPOpenIddict.AspNetCore (free) or Duende.IdentityServer commercial licenceIssue OAuth/OIDC tokens yourself when Entra ID / Okta isn't an option
// Protect an API with Entra ID (reads TenantId, ClientId from the "AzureAd" section)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

// Service-to-service with a managed identity: no passwords or secrets in config
var blobs = new BlobServiceClient(
    new Uri("https://mystore.blob.core.windows.net"), new DefaultAzureCredential());

PIM / just-in-time access, access reviews and conditional access are Entra platform features with no client library. Configure them in the portal or with Graph.

IAM in 4 words: right access, right time ⏰🔑

👤 Who are you? (authentication + MFA)

🛂 What can you do? (RBAC / ABAC)

⏳ For how long? (just-in-time, access reviews)

🚪 What happens when you leave? (automated de-provisioning)

Most breaches aren't hacks. They're logins with too much access.

#IAM #CyberSecurity #ZeroTrust

14.3 SSO protocols: SAML vs OAuth 2.0 vs OIDC Added

SAML and OIDC log users in (single sign-on). OAuth 2.0 grants an app limited access to an API.

SAML 2.0OAuth 2.0OpenID Connect
PurposeAuthentication / SSOAuthorization (delegated access)Authentication on top of OAuth2
TokenXML assertionAccess token (JWT or opaque)ID token (JWT) + access token
Typical useEnterprise web SSO (legacy SaaS)API accessModern web and mobile login, SSO
Mobile / SPA friendlyPoorYesYes
How to implement it
ProtocolLibrary / APINotes
OIDCMicrosoft.AspNetCore.Authentication.OpenIdConnectGeneric OIDC login with any IdP (Entra ID, Okta, Ping, Auth0)
OIDC with Entra IDMicrosoft.Identity.Web (AddMicrosoftIdentityWebApp)Easier setup for Entra, plus token caching for calling APIs
OAuth 2.0 (API side)Microsoft.AspNetCore.Authentication.JwtBearerValidate the access token on every API call
SAML 2.0Sustainsys.Saml2.AspNetCore2 or ITfoxtec.Identity.Saml2.MvcCore (both free, open source)Microsoft doesn't ship a SAML handler for ASP.NET Core. Often simpler: let Entra ID federate the SAML IdP and your app speaks OIDC only
builder.Services.AddAuthentication(o =>
{
    o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;          // session
    o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;  // login
})
.AddCookie()
.AddOpenIdConnect(o =>
{
    o.Authority = "https://login.microsoftonline.com/{tenant-id}/v2.0";
    o.ClientId = builder.Configuration["Oidc:ClientId"];
    o.ClientSecret = builder.Configuration["Oidc:ClientSecret"];
    o.ResponseType = "code";   // authorization code flow (PKCE is on by default)
    o.SaveTokens = true;
});

14.4 Symmetric vs Asymmetric keys

Symmetric: one shared secret key locks and unlocks. Asymmetric: a key pair. What the public key locks, only the private key can unlock.

Symmetric
Plain text
↓ encrypt with 🔑 K
Cipher text
↓ decrypt with the SAME 🔑 K
Plain text
Asymmetric
Plain text
↓ encrypt with Bob's 🔓 public key
Cipher text
↓ decrypt with Bob's 🔐 private key
Plain text
SymmetricAsymmetric
Keys1 shared secretPublic + private pair
SpeedVery fast (bulk data)Slow (100–1000× slower)
Key sizesAES-128 / AES-256RSA 2048–4096, ECC P-256 (≈ RSA 3072)
AlgorithmsAES-GCM, ChaCha20-Poly1305RSA, ECDSA, ECDH, Ed25519
Big problemHow to share the key safely?Performance. Need to trust that a public key really is theirs (PKI)
Used forData at rest, bulk encryption, TLS session dataKey exchange, digital signatures, certificates, JWT RS256

Digital signatures: asymmetric in reverse

Encrypt → use the RECEIVER's public key  |  Sign → use the SENDER's private key

Hybrid encryption (how TLS actually works)

Asymmetric (ECDHE)securely agree a session key
→
Symmetric (AES-GCM)encrypt all the data fast
  • Best of both: asymmetric to exchange keys and sign, symmetric to encrypt the data.
  • JWT: HS256 = symmetric (shared secret). RS256 / ES256 = asymmetric (the API only needs the public key, which is better).
  • Post-quantum crypto (e.g. ML-KEM) is coming, and that work is mostly about asymmetric algorithms.
How to implement it
Library / APIPackageUse it for
AesGcm, ChaCha20Poly1305built-in System.Security.CryptographySymmetric authenticated encryption: secret and tamper-evident
RSA, ECDsa, ECDiffieHellmanbuilt-in System.Security.CryptographyAsymmetric: RSA-OAEP encryption, signatures, key agreement
RandomNumberGeneratorbuilt-inGenerate keys and nonces. Never use System.Random for crypto
Post-quantum: MLKem, MLDsa, SlhDsa, CompositeMLDsabuilt-in (.NET 10)ML-KEM key exchange and PQC signatures. Need OpenSSL 3.5+ (Linux) or Windows with PQC support; check IsSupported. SlhDsa and CompositeMLDsa (and some members of the others) are [Experimental] (SYSLIB5006)
Key Vault keysAzure.Security.KeyVault.Keys (CryptographyClient)Sign, encrypt or wrap keys with a private key that never leaves the vault / HSM
// Symmetric: AES-256-GCM
byte[] key   = RandomNumberGenerator.GetBytes(32);
byte[] nonce = RandomNumberGenerator.GetBytes(12);   // unique per message, never reuse with the same key
byte[] plain = "pay 100 to Bob"u8.ToArray();
byte[] cipher = new byte[plain.Length];
byte[] tag    = new byte[16];
using (var aes = new AesGcm(key, tagSizeInBytes: 16))
    aes.Encrypt(nonce, plain, cipher, tag);          // store nonce + cipher + tag
// aes.Decrypt(nonce, cipher, tag, output) throws if anything was changed

// Asymmetric: sign with the private key, verify with the public key
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
byte[] signature = ecdsa.SignData(plain, HashAlgorithmName.SHA256);
bool valid = ecdsa.VerifyData(plain, signature, HashAlgorithmName.SHA256);

Symmetric vs Asymmetric encryption, using padlocks 🔐

Symmetric: you and I have copies of the same key. Fast, but how do I get you the key safely?

Asymmetric: I hand out open padlocks (public key). Anyone can lock a box for me, but only my private key opens it.

TLS uses both: padlocks to swap a shared key, then the shared key for speed.

#Cryptography #CyberSecurity #TLS

14.5 Hashing vs Encryption vs Encoding vs Tokenisation Added

Four ways to transform data. Only encryption is meant to be reversed with a key.

Reversible?PurposeExamples
EncodingYes, by anyoneFormat compatibility. Not securityBase64, URL encoding
EncryptionYes, with the keyConfidentialityAES, RSA
HashingNo (one-way)Integrity, password storageSHA-256. Passwords: bcrypt, scrypt, Argon2 + salt
HMACNoIntegrity + authenticity with a shared secretHMAC-SHA256 (webhooks)
TokenisationOnly through the token vaultReplace sensitive data with a meaningless tokenCard PAN → token (cuts PCI DSS scope)
MaskingNoDisplay or test data**** **** **** 1234
How to implement it
TransformLibrary / APINotes
Encodingbuilt-in Convert.ToBase64String, Base64Url, Uri.EscapeDataStringFormat only. Anyone can decode it
Hashing / HMACbuilt-in SHA256.HashData, HMACSHA256.HashData, CryptographicOperations.FixedTimeEqualsIntegrity checks and webhook signatures. Compare in constant time
Password hashingbuilt-in PasswordHasher<TUser> (ASP.NET Core Identity), Rfc2898DeriveBytes.Pbkdf2; OSS: BCrypt.Net-Next, Konscious.Security.Cryptography.Argon2Slow, salted hashes. Never plain SHA-256 for passwords
Encryption (app level)built-in Data Protection (IDataProtector), AesGcmReversible protection for cookies, tokens and fields. Data Protection handles keys and rotation for you
TokenisationNo standard libraryCall a token vault (payment provider, card vault service). Your system stores only the token
// Verify a webhook signature (HMAC) in constant time
byte[] expected = HMACSHA256.HashData(webhookSecret, requestBody);
bool valid = CryptographicOperations.FixedTimeEquals(expected, Convert.FromHexString(signatureHeader));

// Password storage: salted, slow key derivation
byte[] salt = RandomNumberGenerator.GetBytes(16);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, 600_000, HashAlgorithmName.SHA256, 32);

14.6 TLS, mTLS & PKI Added

TLS encrypts data in transit and proves the server's identity with a certificate. mTLS makes the client prove its identity too.

Chain of trust
Root CAin OS/browser trust store
→ signs
Intermediate CA
→ signs
Leaf certapi.bank.com
  • Use TLS 1.2+ (prefer 1.3). Disable old ciphers.
  • Certificate expiry causes many outages. Automate renewal (Key Vault, cert-manager, ACME) and monitor expiry dates.
  • mTLS is common for B2B, open banking and service meshes.
How to implement it
Library / APIPackageUse it for
Kestrel HTTPSbuilt-in ConfigureHttpsDefaults, ClientCertificateModeServer TLS; RequireCertificate turns on mTLS
Certificate authenticationMicrosoft.AspNetCore.Authentication.CertificateValidate the client cert and turn it into a ClaimsPrincipal
X509 APIsbuilt-in X509CertificateLoader (.NET 9+), X509Chain, CertificateRequestLoad certs, check the chain of trust, create CSRs and self-signed test certs
Outbound mTLSbuilt-in SocketsHttpHandler.SslOptions with IHttpClientFactory; SslStream for raw TCPPresent a client certificate to a partner API
Certificate lifecycleAzure.Security.KeyVault.CertificatesIssue, auto-renew and fetch certificates from Key Vault
// Server: TLS 1.2+ and require a client certificate (mTLS)
builder.WebHost.ConfigureKestrel(k => k.ConfigureHttpsDefaults(h =>
{
    h.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
    h.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
}));
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(o => o.AllowedCertificateTypes = CertificateTypes.Chained);

// Client: send our certificate to a partner API
var cert = X509CertificateLoader.LoadPkcs12FromFile("client.pfx", pfxPassword);
builder.Services.AddHttpClient("partner").ConfigurePrimaryHttpMessageHandler(() =>
{
    var handler = new SocketsHttpHandler();
    handler.SslOptions.ClientCertificates = new X509CertificateCollection { cert };
    return handler;
});

Behind a load balancer or App Service, TLS often ends before your app. The client cert then arrives in a header; use AddCertificateForwarding to read it.

14.7 Secrets & key management Added

Never hard-code secrets. Store them in a vault, access them by identity, rotate them, and audit every use.

Envelope encryption
Data
→ encrypted by
DEKdata encryption key
→ encrypted by
KEKkey encryption key, in HSM / Key Vault

Data protection states

At rest

Disk, DB (TDE), blob encryption.

In transit

TLS / mTLS everywhere, including internal traffic.

In use

Confidential computing (secure enclaves), column-level encryption.

How to implement it
Library / APIPackageUse it for
Key Vault config providerAzure.Extensions.AspNetCore.Configuration.SecretsLoad vault secrets into IConfiguration, using a managed identity
Key Vault clientsAzure.Security.KeyVault.Secrets, .Keys, .CertificatesRead secrets; CryptographyClient.WrapKeyAsync / UnwrapKeyAsync for envelope encryption with an HSM-backed KEK
User secretsbuilt-in dotnet user-secretsLocal development only. Keeps secrets out of the repo
HashiCorp VaultVaultSharpSecrets, dynamic DB credentials and the Transit (encryption) engine
Column encryptionMicrosoft.Data.SqlClient + Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProviderSQL Always Encrypted: data stays encrypted in the DB; the column master key lives in Key Vault
var credential = new DefaultAzureCredential();   // managed identity in Azure, your login locally
builder.Configuration.AddAzureKeyVault(new Uri("https://my-vault.vault.azure.net/"), credential);

// Envelope encryption: a local DEK protected by a KEK that never leaves Key Vault
var kek = new CryptographyClient(new Uri(keyId), credential);
byte[] dek = RandomNumberGenerator.GetBytes(32);           // encrypt the data with this (AesGcm)
WrapResult wrapped = await kek.WrapKeyAsync(KeyWrapAlgorithm.RsaOaep256, dek);
// store wrapped.EncryptedKey next to the data; UnwrapKeyAsync gives the DEK back

14.8 Zero Trust Added

"Never trust, always verify." Being inside the network doesn't make you trusted.

1. Verify explicitly

Authenticate and authorise every request using identity, device health, location and risk.

2. Least privilege

Just-in-time and just-enough access. Time-bound.

3. Assume breach

Micro-segmentation, encrypt everything, monitor, limit the blast radius.

How to implement it
PrincipleLibrary / APINotes
Verify explicitlyMicrosoft.AspNetCore.Authentication.JwtBearer / Microsoft.Identity.WebEvery service validates the token on every call, even "internal" ones
Workload identityAzure.Identity (ManagedIdentityCredential, WorkloadIdentityCredential)Services prove who they are without shared secrets
Least privilegebuilt-in authorization policies + resource-based handlersCheck scopes/roles per endpoint and ownership per record
Encrypt everywherebuilt-in Kestrel mTLS, Microsoft.AspNetCore.Authentication.CertificatemTLS between services (or let a service mesh do it)
Assume breachOpenTelemetry + audit loggingLog and trace every access so you can spot misuse. Private endpoints and network rules are platform settings

14.9 OWASP API Security Top 10 (2023) Added

The ten most critical API security risks, according to OWASP.

#RiskIn plain words / fix
API1Broken Object Level Authorization (BOLA)Changing /accounts/42 to /43 shows someone else's data. Check ownership on every object
API2Broken AuthenticationWeak tokens or login flows. Use a standard IdP, MFA, validated JWTs
API3Broken Object Property Level AuthorizationExposing or allowing edits to fields you shouldn't (mass assignment). Allowlist fields
API4Unrestricted Resource ConsumptionNo limits → DoS and big bills. Rate limits, quotas, payload size caps
API5Broken Function Level AuthorizationNormal user calls admin endpoints. Role checks per function
API6Unrestricted Access to Sensitive Business FlowsBots abusing flows (ticket scalping). Bot detection, flow limits
API7Server-Side Request Forgery (SSRF)API fetches an attacker-supplied URL. Validate and allowlist destinations
API8Security MisconfigurationVerbose errors, open CORS, missing TLS. Harden and scan
API9Improper Inventory ManagementForgotten old or shadow APIs. Keep an API inventory, retire old versions
API10Unsafe Consumption of APIsBlindly trusting third-party API data. Validate and sanitise
How to implement it
RiskLibrary / APINotes
BOLA / BFLA (API1, API5)built-in IAuthorizationService.AuthorizeAsync(user, resource, policy)Check ownership of each record, and policies on each endpoint
Resource consumption (API4)built-in AddRateLimiter, Kestrel MaxRequestBodySizeRate limits, size limits, and paging caps on list endpoints
Property-level auth / mass assignment (API3)built-in request/response DTOs + AddValidation() (.NET 10 minimal APIs); OSS: FluentValidationNever bind straight to EF entities. Return only the fields the caller may see
SSRF (API7)built-in IHttpClientFactory named clientsFixed base addresses and an allow-list of hosts. Never fetch a URL the user supplied
Misconfiguration / inventory (API8, API9)NetEscapades.AspNetCore.SecurityHeaders, Microsoft.AspNetCore.OpenApi, NuGet audit on dotnet restoreSecurity headers, an OpenAPI document for every API, and warnings for vulnerable packages
builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    o.AddFixedWindowLimiter("api", w => { w.PermitLimit = 100; w.Window = TimeSpan.FromMinutes(1); });
});
builder.Services.AddValidation();   // DataAnnotations on minimal API parameters

var app = builder.Build();
app.UseRateLimiter();

app.MapGet("/orders/{id:int}", async (int id, ClaimsPrincipal user,
    IAuthorizationService auth, AppDbContext db) =>
{
    var order = await db.Orders.FindAsync(id);
    if (order is null) return Results.NotFound();
    var result = await auth.AuthorizeAsync(user, order, "OrderOwner");   // BOLA check
    return result.Succeeded ? Results.Ok(order.ToDto()) : Results.Forbid();
}).RequireAuthorization().RequireRateLimiting("api");

14.10 DevSecOps & software supply chain security Added

Build security checks into every step of delivery, and be able to prove that what runs in production is exactly what you built, from parts you trust.

The scanners and what they find

🔍 SAST

Static analysis of your source code for bugs like SQL injection or weak crypto. Runs on every PR. Tools: CodeQL, SonarQube, .NET analyzers.

🎯 DAST

Attacks the running app from the outside, like a hacker would. Finds config and runtime issues. Tools: OWASP ZAP, Burp Suite.

📦 SCA (dependency scanning)

Known CVEs and licence problems in third-party packages, including transitive ones. Tools: NuGet Audit, Dependabot, Snyk.

🔑 Secret scanning

Keys, tokens and passwords in code and git history. Use push protection to stop them before they land. Tools: GitHub secret scanning, Gitleaks.

🏗️ IaC scanning

Misconfigured Terraform, Bicep or Kubernetes YAML: public storage, open ports, no encryption. Tools: Checkov, Trivy.

🐳 Container image scanning

CVEs in the base image and OS packages. Rebuild often on a small, patched base image. Tools: Trivy, Grype, Defender for Cloud.

Supply chain: prove what you ship

PracticeWhat it isWhy it matters
SBOMA machine-readable "ingredients list" of every component and version. Formats: SPDX (Linux Foundation) and CycloneDX (OWASP)When the next Log4Shell lands, "are we affected?" takes minutes, not weeks
Signed builds & imagesSign artifacts and images: Sigstore cosign (keyless signing with OIDC and a public transparency log) or Notation (Notary Project, keys in a vault)The cluster can refuse anything that wasn't signed by your pipeline
SLSA levelsA framework for build integrity. Build L1: provenance exists. L2: signed provenance from a hosted build platform. L3: a hardened, isolated build platformProves where and how an artifact was built, so tampering is detectable
Dependency confusionAn attacker publishes a public package with the same name as your internal one, often with a higher version, and your build picks it upFix: map package names to feeds, use private feeds with controlled upstreams, reserve your prefix
TyposquattingLook-alike names (Newtonsoft.Jsn) that hide malwareFix: review new dependencies, lock files, allow-lists
Key & secret rotationRotate signing keys, secrets and tokens on a schedule and immediately on suspicion. Prefer short-lived credentialsPipelines should use OIDC workload identity federation, not long-lived cloud secrets
  • Shift left to catch issues cheaply. Shift right to catch what only shows up in production. You need both.
  • Gate on severity, with a time-boxed exception process. Otherwise teams learn to ignore the scanner.
  • An SBOM is only useful if you store it and re-scan it when new CVEs are published.
  • Most of what you ship is code you didn't write. Treat dependencies as part of your attack surface.
How to implement it
CheckLibrary / toolNotes
SCAbuilt-in NuGet Audit on dotnet restore (NuGetAudit, NuGetAuditMode, NuGetAuditLevel); dotnet list package --vulnerableProjects targeting net10.0 audit transitive packages by default (NuGetAuditMode=all). dotnet package update --vulnerable is new in the .NET 10 SDK
SASTbuilt-in .NET code analyzers (security rules CA2100, CA3xxx, CA5xxx); GitHub CodeQL (supports C#)Many security rules are off by default: turn them on with AnalysisModeSecurity. SecurityCodeScan has had no release since 2022
Dependency confusionbuilt-in package source mapping (nuget.config) + lock files (RestorePackagesWithLockFile)Internal names can only come from the internal feed. CI runs dotnet restore --locked-mode
Signed packagesbuilt-in dotnet nuget verify; trustedSigners in nuget.configOnly accept packages signed by authors or repositories you trust
SBOMMicrosoft.Sbom.DotNetTool (sbom-tool, SPDX) or CycloneDX (.NET tool, dotnet-CycloneDX)Generate an SBOM in the pipeline and publish it with the build
UpdatesDependabot (NuGet ecosystem) or RenovateAutomatic PRs for vulnerable and outdated packages
<!-- Directory.Build.props: audit everything, pin everything -->
<PropertyGroup>
  <NuGetAudit>true</NuGetAudit>
  <NuGetAuditMode>all</NuGetAuditMode>        <!-- default for net10.0 -->
  <NuGetAuditLevel>moderate</NuGetAuditLevel>
  <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
  <AnalysisModeSecurity>All</AnalysisModeSecurity>
</PropertyGroup>

<!-- nuget.config: Contoso.* packages may ONLY come from the internal feed -->
<packageSourceMapping>
  <packageSource key="nuget.org">
    <package pattern="*" />
  </packageSource>
  <packageSource key="internal">
    <package pattern="Contoso.*" />
  </packageSource>
</packageSourceMapping>

With TreatWarningsAsErrors, the new transitive audit warnings (NU1901–NU1904) can break restores after upgrading to .NET 10. Fix the root package (dotnet nuget why) rather than suppressing the warning.

Most of what you ship is code you didn't write 📦

🔍 SAST on every PR. DAST against staging.

📦 Dependency scanning + lock files: know every package, block known CVEs.

🔑 Secret scanning with push protection.

🧾 An SBOM for every build, so "are we affected?" takes minutes.

✍️ Sign your images, and only run signed ones.

Shift left to catch it cheaply. Shift right to catch what only shows up in prod.

#DevSecOps #SupplyChainSecurity #SBOM #AppSec

⚡ Quick-fire Q&A

Why not encrypt passwords instead of hashing?

Encrypted passwords can be decrypted if the key leaks. Slow, salted hashes (bcrypt/Argon2) can't be reversed, and they make brute force expensive.

RBAC vs ABAC?

RBAC grants access by role and is simple, but leads to role explosion. ABAC uses attributes and context, which is flexible and fine-grained but more complex to govern.

What does a managed identity solve?

It removes credentials from code and config. The cloud platform issues and rotates tokens for the workload automatically.

What is an SBOM, and why does it matter?

A software bill of materials: a machine-readable list of every component and version in a build, in SPDX or CycloneDX format. When a new vulnerability is published, you can search your SBOMs and know in minutes which systems are affected. Regulators and customers increasingly ask for it too.

What is dependency confusion, and how do you prevent it in .NET?

An attacker publishes a public package with the same name as an internal one, and the build pulls the public version. In .NET, use NuGet package source mapping so internal prefixes only resolve from the internal feed, commit lock files and restore with --locked-mode in CI, and reserve your package ID prefix on nuget.org.

← 13 · Architecture Practice