Network Security: Protecting Data in Transit
Understand the security reasoning behind HTTPS/TLS, certificate validation, and App Transport Security; apply sound patterns for token-based authentication, secure token storage, and secure cookies; and weigh the real trade-offs of certificate pinning, avoiding hardcoded secrets, and never logging sensitive data.
The earlier lesson on URL and Networking walked through NSURLSession, requests, and the HTTP round trip, with only a brief mention that TLS secures the connection along the way. This lesson goes back to that one sentence and takes it seriously: every network request your app makes is an opportunity for data to leak, be tampered with, or be misused, and treating that as a real design concern — not just something the platform quietly handles for you — is what separates an app that happens to work from one that's actually safe to use with real credentials and real personal data. This lesson covers what TLS actually protects and why, how certificate validation and App Transport Security enforce that protection by default (and why fighting those defaults is a decision to make carefully, not casually), how to think about authentication tokens and where they should and shouldn't live, the real trade-offs of certificate pinning, and the discipline — genuinely a discipline, not a footnote — of never letting secrets or sensitive data leak through logs.
HTTPS and TLS: What an Encrypted Channel Actually Buys You
TLS (Transport Layer Security, the modern successor to SSL, and the 'S' in HTTPS) establishes an encrypted, authenticated channel between your app and a server before any application data — the actual HTTP request and response — travels over it. 'Encrypted' means the bytes traveling over the network are unintelligible to anyone who intercepts them without the right keys, protecting against eavesdropping: an attacker on the same coffee-shop Wi-Fi, or anyone else positioned to observe traffic between your app and the server, sees only ciphertext, not the login credentials, tokens, or personal data your request actually carries. 'Authenticated' means the connection also proves the server's identity — your app is talking to the actual server it thinks it's talking to, not an impostor silently standing in the middle of the conversation.
Both properties matter, and it's worth being explicit that they solve different problems: encryption alone, without authentication, would still let an attacker perform a man-in-the-middle attack by presenting themselves as the real server, decrypting and re-encrypting traffic as it passes through them, invisible to both ends unless something checks that the party on the other end is really who it claims to be. TLS's certificate-based handshake is what supplies that authentication piece, which is exactly why certificate validation — the next section — is not a separate, optional add-on to TLS but the mechanism that makes the encryption actually trustworthy in the first place.
On Apple's platforms, NSURLSession performs the entire TLS handshake for you automatically whenever a request's URL uses https:// — you do not write TLS handshake code yourself. That automatic handling is a feature, not a limitation: correctly implementing a cryptographic protocol like TLS by hand is genuinely difficult to get right, and letting the platform's well-audited, continuously updated implementation handle it is safer than any custom implementation an individual app team would realistically build and maintain.
Certificate Validation: Why NSURLSession's Default Trust Checking Matters
When NSURLSession connects to an HTTPS server, the server presents a certificate as part of the TLS handshake, and by default the system validates that certificate through standard trust-chain checking against a set of trusted root certificate authorities (CAs) built into the OS. That chain-of-trust check confirms several things at once: that the certificate was issued by a CA the system trusts, that it hasn't expired, that it hasn't been revoked, and that it actually matches the hostname your app is connecting to — a certificate issued for example.com presented while connecting to not-example.com fails validation, exactly the check that stops the man-in-the-middle scenario described above from succeeding silently.
This default validation runs automatically and silently succeeds for any server with a properly issued, valid certificate from a trusted CA — the overwhelming majority of real-world HTTPS servers — which is precisely why it's easy to forget it's happening at all. The moment it becomes visible is when something's wrong: an expired certificate, a self-signed certificate used during development, or a genuine attack, all surface as the same kind of TLS trust failure, and NSURLSession refuses to complete the connection rather than silently proceeding with an unverified server identity.
It is tempting, especially during development against a local server with a self-signed certificate, to disable this validation — implementing URLSession:didReceiveChallenge:completionHandler: to unconditionally call the completion handler with NSURLSessionAuthChallengeUseCredential and a credential built from whatever certificate was presented, regardless of whether it's trusted. Doing this even briefly is genuinely dangerous, and not only because it's easy to forget to remove before shipping: it disables the exact mechanism that distinguishes 'this is genuinely the server I intend to talk to' from 'this is anyone who's willing to present a certificate at all', for every single request that code path handles, silently and completely. If a self-signed certificate is unavoidable for a specific development server, the far safer approach is to validate that specific certificate explicitly by comparing it against a known, expected value, rather than disabling validation outright for every possible server the code might ever connect to.
App Transport Security: The Platform's Default-Deny Policy
App Transport Security (ATS) is a platform-level policy, enforced by iOS and macOS themselves rather than by your own code, that requires network connections to use HTTPS with reasonably modern TLS by default. If your app attempts a plain http:// connection, or an HTTPS connection using outdated or weak TLS parameters, ATS blocks it before the request is even sent, and the app must explicitly opt out for that specific case via an NSAppTransportSecurity exception in Info.plist to make it work at all.
It's worth understanding the actual reasoning ATS encodes, not just treating it as a hurdle to clear: the policy exists precisely because plain HTTP traffic is unprotected exactly the way the first section of this lesson described — readable and tamperable by anyone positioned to intercept it — and defaulting every app on the platform to HTTPS closes off an entire, historically very common class of vulnerability (apps quietly sending credentials, tokens, or personal data in the clear, often without the app's own developers realizing it) at the operating-system level, for every app, without relying on each individual developer to remember to opt into safety themselves.
Adding an ATS exception to allow a plain HTTP connection — NSAllowsArbitraryLoads for a blanket exception, or a scoped NSExceptionDomains entry for a specific host — should be treated as a deliberate, narrow, and ideally temporary decision, not a default fix for 'my network request isn't working.' A common, legitimate reason to encounter this at all is a third-party or legacy backend that genuinely doesn't yet support HTTPS; even then, the right response is usually to scope the exception as tightly as possible to that one domain, push for that backend to be updated to HTTPS, and never reach for the blanket NSAllowsArbitraryLoads exception, which disables ATS's protection across the entire app for every domain it talks to, not just the one that actually needed an exception.
Authentication: Bearer Tokens and Token-Based Flows
Most modern app-to-server authentication is token-based rather than sending a username and password on every single request. A typical flow: the user authenticates once, usually by submitting credentials to a login endpoint (itself over HTTPS, obviously), and the server responds with a token — often called an access token — that the app then attaches to every subsequent request as proof of who's asking. The most common mechanism for attaching it is the Authorization HTTP header with the Bearer scheme: Authorization: Bearer <token>, a convention NSURLRequest supports simply by setting that header's value directly with setValue:forHTTPHeaderField:.
OAuth-style flows extend this basic pattern with a few additional pieces worth knowing conceptually, even without implementing a full OAuth client from scratch in this lesson: an access token that's typically short-lived and used for actual API calls, and a longer-lived refresh token, stored more carefully, used only to obtain a new access token once the current one expires, without requiring the user to log in again with their password. This separation limits the damage if an access token leaks — it's only useful for a limited window — while keeping the refresh token, the more sensitive of the two, out of the frequent, high-volume traffic pattern the access token is used in.
The key conceptual point to carry forward, regardless of the exact flow, is that a bearer token is exactly what its name says: whoever bears it — presents it — is treated as authenticated, with no further proof required. There's no additional signature or password check tied to the token itself on most simple bearer-token schemes; if a token leaks to an attacker, that attacker can use it exactly as if they were the legitimate user, for as long as the token remains valid. That single fact is what makes the next section, on where tokens should live, a genuine security question and not a matter of convenience.
Token Handling: Where Tokens Should and Should Not Live
Because possessing a bearer token is, functionally, equivalent to being logged in as the user it belongs to, where you store that token between app launches is a direct security decision, not an implementation detail. The earlier Keychain lesson in this course covered exactly the storage mechanism built for this: Keychain Services stores items encrypted at rest, gated behind accessibility and access-control settings, and is the correct, intended home for an authentication token exactly the same way it's the correct home for a password or an API key — this isn't a new rule, it's the same rule from that lesson applied to the specific case of a token your networking layer manages.
NSUserDefaults, by contrast, is the wrong place for a token, for precisely the same reasons that lesson gave for passwords and API keys: its backing plist is not encrypted, and anyone with file-system access to the device — including, on a jailbroken device or during certain kinds of debugging and backup extraction, someone who isn't the device's legitimate owner — can read it in plain text. A token sitting in NSUserDefaults is barely more protected than a token hardcoded directly into your source, which is the other pattern to rule out categorically: a token, unlike a fixed API key, is generated per-user at login time and by definition cannot be hardcoded at all, but the same instinct that makes hardcoding a secret tempting can just as easily lead to stashing a token somewhere convenient-but-unprotected like NSUserDefaults 'just for now' — and that 'for now' is exactly how tokens end up shipping unprotected.
The third rule, distinct from where a token is stored at rest, is that a token should never appear in a log statement, not even during debugging, not even 'temporarily.' This deserves its own full treatment and gets one later in this lesson's logging section, but it's worth flagging here as part of the complete picture of responsible token handling: storage discipline (Keychain, never UserDefaults or source code) and logging discipline (never printed, anywhere) are two separate rules that both have to hold for a token to actually be handled safely end to end.
Secure Cookies and Certificate Pinning Trade-offs
Not every app manages its own session state with a bearer token — some talk to backends that use traditional cookie-based sessions instead, where the server sets a session identifier via a Set-Cookie response header and NSHTTPCookieStorage (used automatically by NSURLSession by default) sends it back on subsequent requests. When cookies are in play, two attributes on the cookie itself matter for security: Secure, which tells the browser or networking stack to send that cookie only over HTTPS connections, never over a plain HTTP fallback, and HttpOnly, which prevents client-side script from reading the cookie's value at all (a protection more directly relevant to web contexts than to a native Objective-C app, but worth recognizing if your app embeds a WKWebView talking to the same backend). Both are set by the server in the Set-Cookie header, not configured from the client side, but understanding what they do is useful for evaluating whether a backend you're integrating with is handling session cookies responsibly.
Certificate pinning goes a step further than the default trust-chain validation this lesson already covered: instead of trusting any certificate issued by any CA the system trusts, a pinned connection is configured to trust only a specific certificate, or a specific public key, that you've bundled into the app ahead of time. The security benefit is real and specific: it protects against a compromised or misissued CA certificate somewhere in the system's trust store — an attacker who manages to get a fraudulent certificate for your domain issued by some CA the OS trusts (a real, if rare, category of incident that has happened to major CAs) would still fail your app's pinning check, because pinning ignores the general trust chain entirely and checks only for the one specific certificate or key you've pinned.
That benefit comes with a real, and often underestimated, operational cost: certificates expire and get rotated periodically as a matter of routine server operations, and if your server's certificate rotates to a new one and your already-shipped app is still pinned to the old certificate's fingerprint, every pinned connection from every copy of that app version starts failing — effectively bricking the app's networking for anyone who hasn't updated, until either the server rotates back (not a real option) or you ship an app update with the new pin (which takes time, and depends on users actually updating). Pinning is not something to adopt casually; it needs a genuine rotation strategy from day one — commonly pinning to a public key that can survive a certificate renewal using the same key pair, or bundling multiple pins (current and next) ahead of a planned rotation — and a team that isn't prepared to maintain that discipline over the app's lifetime is often better served by the platform's default trust-chain validation alone, which, for the overwhelming majority of apps, already provides strong protection without this specific operational risk.
Secrets: Why They Don't Belong in the App Binary
It's a common instinct to embed an API key or a shared secret directly into source code as a string constant, compiled straight into the app binary you ship to the App Store. This feels safe because the string isn't visible anywhere in your source repository's public-facing surface and isn't something a user browsing the app on their device would stumble across by accident. It is not actually safe: an app binary is not opaque. Anyone who downloads your app can extract the compiled binary and run string-extraction tools against it, or go further with proper static analysis and disassembly tools, and a plain string constant embedded in your code is trivially recoverable this way — it sits in the binary's data section essentially unobscured, findable by a simple search for readable text.
The later App Security module in this course covers reverse-engineering awareness — how an attacker approaches inspecting a compiled app binary at all — in real depth, and this point connects directly to that content without needing to repeat it here: any secret embedded directly in your app, however it's obfuscated or however cleverly you think you've hidden it, should be assumed recoverable by someone motivated enough to look, because the app genuinely does run entirely on hardware and under an operating system the attacker fully controls. The practical implication for network security specifically is that any credential meant to stay actually secret — a private API key granting elevated access, a signing secret, anything whose exposure would let someone impersonate your backend or abuse a paid API on your account's dime — belongs on your server, brokered through an authenticated request from your app, never compiled directly into a binary you distribute to every device that installs it.
Logging and Sensitive Data: The Closing Discipline
This deserves to be stated as plainly and forcefully as anything in this lesson: never log a full authentication token, a password, or full request/response bodies that may contain sensitive personal data, even to a local debug log during development, even 'just this once' to diagnose a bug. NSLog(@"Token: %@", token); feels harmless in the moment — you're looking at your own device's console, debugging your own code — but that habit is exactly how sensitive data ends up somewhere it was never meant to be: logs get swept into crash reports that get uploaded to a crash-reporting service; a support engineer asks a user to screenshot their console output while diagnosing an unrelated bug and now has that user's token in a support ticket; a third-party logging or analytics SDK integrated for entirely unrelated reasons captures console output wholesale and ships it to its own servers, outside your control and outside your knowledge, if you weren't careful about what got logged in the first place.
The practical discipline is straightforward to state even if it takes real consistency to maintain: log that a request happened, its endpoint, its status code, and timing — the operational information useful for debugging — but never log the actual value of an Authorization header, a token, a password field, or a full response body from an endpoint that might return personal data. Where you genuinely need to inspect a token's presence during debugging, log a truncated or masked form — the first few characters plus ellipsis, or simply whether it's present or nil — rather than the value itself, exactly the same instinct that leads a well-designed system to mask a credit card number down to its last four digits rather than displaying it in full.
Zooming out, this final point is really the synthesis of the entire lesson rather than a narrow addendum about NSLog: every value that crosses your network boundary — request bodies, response bodies, headers, tokens, cookies — is worth a deliberate moment of thought about who could conceivably see it and what happens if they do, across its entire lifecycle from the moment it's created, through transit, through storage on the device, and through every diagnostic tool that might capture it along the way. TLS protects data in transit, App Transport Security enforces that protection by default, Keychain protects tokens at rest, and disciplined logging protects data from leaking through the one channel none of those mechanisms cover at all. Treating each of those as a genuine, separate responsibility — rather than assuming that using HTTPS alone makes a network-connected app 'secure' — is what this whole module has been building toward.
Attaching a Bearer Token and Handling a TLS Trust Challenge
Prefer letting NSURLSession's default TLS/certificate validation run unmodified; only override it for a specific, well-understood reason such as certificate pinning with a real rotation plan. A token belongs in Keychain at rest, in the Authorization header in transit, and nowhere in a log statement, ever — not even truncated logging of the raw value. ATS's default is HTTPS-only; an Info.plist exception should be as narrow as possible (a single domain) and treated as a deliberate, temporary decision, not a default fix.
// Attaching a bearer token to a request (retrieved from Keychain, never from
// NSUserDefaults or a hardcoded string):
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *authHeader = [NSString stringWithFormat:@"Bearer %@", token];
[request setValue:authHeader forHTTPHeaderField:@"Authorization"];
// The default, safe posture: let NSURLSession validate the server's certificate
// against the system trust store automatically. Do NOT implement
// URLSession:didReceiveChallenge:completionHandler: to bypass this
// unless you fully understand — and have a real plan for — the trade-off.
// Never do this in shipping code:
// NSLog(@"Authorization header: %@", authHeader); // leaks the full tokenCode Examples
The token is read from Keychain, never from NSUserDefaults or a hardcoded constant, honoring the token-handling storage rule this lesson covers. It's attached to the request as a Bearer value in the Authorization header, the standard token-based authentication pattern. Note what's deliberately absent: there is no NSLog of the token, the header, or the raw response body anywhere in this method — exactly the logging discipline this lesson insists on.
[request setValue:[NSString stringWithFormat:@"Bearer %@", token] forHTTPHeaderField:@"Authorization"];
The first implementation trusts whatever certificate is presented, for every single host the app ever connects to, defeating the entire purpose of TLS's server-authentication guarantee described earlier in this lesson — an attacker performing a man-in-the-middle attack against any request this app makes would sail through undetected. The second implementation scopes any special handling to one specific, known development host and falls back to NSURLSessionAuthChallengePerformDefaultHandling — the system's normal, safe validation — for every other host, which is the shape a real exception should take if one is ever truly necessary.
Common mistake: Unconditionally calling the completion handler with NSURLSessionAuthChallengeUseCredential for every challenge disables certificate validation for the entire app, not just the intended development server.
Pinning against the server's public key hash, rather than the full certificate, is the pattern most resilient to routine certificate renewal, since a renewed certificate using the same key pair still matches the pinned key. Storing pinnedPublicKeyHashes as an array per host, rather than a single value, is what allows a real rotation strategy: a new pin can be added ahead of a planned certificate change, and the app can accept either the current or the next certificate during the transition window, rather than failing the instant the server rotates. This is the concrete shape of the 'real rotation strategy' this lesson insists pinning must have — without a mechanism like this array of accepted pins, a single certificate rotation on the server would break every already-shipped copy of the app pinned to the old certificate.
- (NSURLSessionDataTask *)fetchProfileWithCompletion:(void (^)(NSDictionary *profile, NSError *error))completion {
NSString *token = [KeychainStore stringForKey:@"authToken"]; // from the Keychain lesson's pattern
NSMutableURLRequest *request =
[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api.example.com/v1/me"]];
[request setValue:[NSString stringWithFormat:@"Bearer %@", token]
forHTTPHeaderField:@"Authorization"];
NSURLSessionDataTask *task =
[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
completion(nil, error);
return;
}
NSDictionary *profile = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
completion(profile, nil);
}];
[task resume];
return task;
}// DANGEROUS — do not do this, including "temporarily" during development:
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {
// Unconditionally trusts ANY certificate presented by ANY server,
// completely defeating TLS's authentication guarantee.
NSURLCredential *credential =
[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
// SAFER — let default validation run for every host except a known,
// specific development server, and pin that one server explicitly rather
// than disabling validation globally:
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {
if ([challenge.protectionSpace.host isEqualToString:@"dev.internal.example.com"]) {
…- (BOOL)isTrustedServerTrust:(SecTrustRef)serverTrust forHost:(NSString *)host {
// Pinned keys should be an array so a NEW pin can be added ahead of a planned
// certificate rotation, alongside the CURRENT pin, before the old one expires.
NSArray<NSData *> *pinnedPublicKeyHashes = self.pinnedPublicKeyHashesForHost[host];
if (!pinnedPublicKeyHashes) {
return NO; // unknown host: fail closed rather than silently trusting it
}
NSData *presentedKeyHash = [self publicKeyHashFromTrust:serverTrust];
return [pinnedPublicKeyHashes containsObject:presentedKeyHash];
}Network Security Failures Are Rarely About the Cryptography
In practice, the TLS handshake itself is almost never where a real-world app's network security fails — NSURLSession's default handling is solid, well-audited, and rarely worth second-guessing. The actual, recurring failures this module addresses are procedural: a debug-only certificate bypass that quietly ships in a release build, a token stashed in NSUserDefaults because Keychain's API felt like more setup than a deadline allowed, an API key committed straight into source and compiled into a binary anyone can download and inspect, or a verbose debug log statement that prints a full response body and, months later, ends up captured by a crash-reporting SDK nobody thought to scope out sensitive fields from. Every one of those is avoidable with the specific, concrete discipline this lesson has laid out, and every one of them has caused real, publicly reported incidents at real companies.
Implementing URLSession:didReceiveChallenge:completionHandler: to unconditionally trust every certificate, even temporarily during development, and forgetting (or not managing) to remove it before release.
Let NSURLSession's default trust-chain validation run for normal hosts; if a specific development host truly needs special handling, scope it narrowly to that one host and fall back to default handling for everything else.
Disabling certificate validation defeats TLS's server-authentication guarantee for every request the app makes, not just the one it was meant to fix, silently exposing the app to man-in-the-middle attacks.
Storing an authentication token in NSUserDefaults, or hardcoding a secret directly as a string constant compiled into the app.
Store tokens in Keychain, as covered in the earlier Keychain lesson, and keep server-side secrets (API keys with elevated access, signing secrets) on the server, never embedded in the shipped binary.
NSUserDefaults' backing plist is unencrypted and readable by anyone with file-system access; a hardcoded secret in the binary is recoverable through straightforward static analysis, as the later App Security module covers in depth.
NSLog(@"Login response: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); logging a full authentication response including the token.
Log only operational metadata — endpoint, status code, timing — and, if a token's presence must be verified during debugging, log a truncated or masked form, never the full value.
Logs routinely end up somewhere beyond the original debugging session — crash reports, support screenshots, third-party logging SDKs — and a full token or password logged even once can leak through any of those channels.
Adopting certificate pinning against a single, specific certificate with no plan for what happens when that certificate expires or is rotated on the server.
Pin against a public key hash (which survives a same-key-pair renewal) or maintain multiple accepted pins (current and next) ahead of any planned rotation, and only adopt pinning at all when the team is prepared to maintain that operational discipline.
A certificate rotation with no corresponding app update breaks every pinned connection from every already-shipped copy of the app, effectively bricking its networking until users update — pinning without a rotation strategy trades one risk for another.
What two distinct properties does TLS provide for an HTTPS connection? A. Faster data transfer and automatic compression B. Encryption (protecting data from eavesdropping) and authentication (confirming the server's identity) C. Encryption only — server identity is a separate, unrelated concern D. Guaranteed delivery and automatic retries
B. Encryption (protecting data from eavesdropping) and authentication (confirming the server's identity) — TLS both encrypts data in transit, protecting it from eavesdropping, and authenticates the server's identity via its certificate, which is what prevents a man-in-the-middle attacker from silently impersonating the real server even if the traffic were otherwise encrypted.
Why is unconditionally disabling certificate validation in URLSession:didReceiveChallenge:completionHandler: dangerous, even 'just for development'? A. It has no real effect since NSURLSession ignores custom challenge handling B. It only affects requests to localhost, so it's safe everywhere else C. It defeats TLS's server-authentication guarantee for every request the app makes, exposing it to man-in-the-middle attacks, and risks shipping unnoticed D. It merely slows down network requests without any security impact
C. It defeats TLS's server-authentication guarantee for every request the app makes, exposing it to man-in-the-middle attacks, and risks shipping unnoticed — Unconditionally trusting every presented certificate removes the check that confirms the app is actually talking to the intended server, for every host the code path handles — not just the development server it was meant to accommodate — and such debug shortcuts have a track record of accidentally shipping in release builds.
What is the real reasoning behind App Transport Security's default requirement of HTTPS? A. It's an arbitrary Apple policy with no real security rationale, purely for App Store review purposes B. It exists to reduce Apple's own server bandwidth costs C. Plain HTTP traffic is unprotected and interceptable/tamperable, so defaulting every app to HTTPS closes off a historically common vulnerability class at the OS level D. HTTPS is required only because it's faster than HTTP on modern networks
C. Plain HTTP traffic is unprotected and interceptable/tamperable, so defaulting every app to HTTPS closes off a historically common vulnerability class at the OS level — ATS encodes a genuine security rationale: unencrypted HTTP traffic can be read and tampered with by anyone positioned to intercept it, and requiring HTTPS by default protects every app on the platform from that risk without relying on each developer to opt in individually.
Where should an authentication token be stored between app launches, and why? A. NSUserDefaults, because it's the simplest API for persisting small values B. Hardcoded in source code, so it's always available without any lookup C. Keychain, because it encrypts items at rest, unlike NSUserDefaults' unencrypted plist storage D. In a plain text file in the app's Documents directory for easy debugging
C. Keychain, because it encrypts items at rest, unlike NSUserDefaults' unencrypted plist storage — A bearer token is functionally equivalent to being logged in as its owner; Keychain stores items encrypted at rest with configurable accessibility, matching the same reasoning covered in the earlier Keychain lesson for passwords and API keys, while NSUserDefaults' backing plist is readable in plain text by anyone with file-system access.
What is the real operational trade-off of certificate pinning, weighed against its security benefit? A. Pinning has no downside — it should always be used for every network connection B. Pinning protects against a compromised or misissued CA certificate, but a certificate rotation with no matching app update can break every pinned connection from already-shipped app copies C. Pinning makes requests significantly slower with no meaningful security benefit D. Pinning is only relevant for apps that don't use HTTPS at all
B. Pinning protects against a compromised or misissued CA certificate, but a certificate rotation with no matching app update can break every pinned connection from already-shipped app copies — Pinning ignores the general CA trust chain and checks only a specific bundled certificate or key, which protects against a compromised or misissued CA — but if the server's real certificate rotates without a corresponding app update carrying the new pin, every already-shipped copy pinned to the old certificate loses connectivity, so pinning requires a genuine rotation strategy.
Why should a full authentication token never be written to a local debug log, even during development? A. NSLog calls are automatically stripped from release builds, so it's harmless during development B. Logging is computationally expensive and will noticeably slow down the app C. Logs can leak beyond the original debugging session through crash reports, support screenshots, or third-party logging SDKs, exposing the token the same way as a direct leak would D. Tokens are not valid string data and will corrupt the log output
C. Logs can leak beyond the original debugging session through crash reports, support screenshots, or third-party logging SDKs, exposing the token the same way as a direct leak would — A logged token is exactly as usable to whoever eventually sees it as the token itself, and logs routinely travel further than a developer's own console — into crash reports, support tickets with screenshots, or third-party services — so a token logged even once during debugging can leak just as seriously as one exposed any other way.
Your app currently stores its refresh token in NSUserDefaults and logs the full Authorization header on every request via NSLog for 'debugging convenience.' List the specific changes you would make to bring this in line with the practices covered in this lesson, and briefly justify each one.
Move the refresh token (and the access token, if it's also persisted) out of NSUserDefaults and into Keychain, using accessibility/access-control settings appropriate to how the app uses it, since NSUserDefaults' plist is unencrypted and readable by anyone with file-system access. Remove the NSLog of the full Authorization header entirely; if request debugging is still needed, log only the endpoint, HTTP method, and status code, or at most a masked form of the token (e.g. its first few characters plus ellipsis) rather than the full value, since a fully logged token is exactly as usable to an attacker as the token itself. Leave the access-token attachment mechanism (the Bearer Authorization header on each request) as is — that part is standard and correct; only its storage at rest and its appearance in logs need to change.
Address storage location, what (if anything) is safe to log instead, and whether any change here also affects how the access token itself should be attached to outgoing requests.