class: center, middle # "No more secrets: how to secure modern webapps without sharing your keys" ??? Craig Nicol is a technical architect at Screenmedia, and a developer with over 20 years experience in a variety of technologies, with a particular interest in interfaces and infrastructure, especially when they need to be secured. (The mermaid diagrams need to be inserted as PNGs as there's a conflict between remarkjs and mermaid when there's multiple diagrams) --- # The requirement Logging into the site and calling APIs securely .callout-image[🕵️] .footer[If you're viewing these slides yourself, press `[p]` to see the presenter notes] ??? i.e. how does that backend know that I am me? --- background-image: url(mermaid-classic-transparent.png) ??? Cookies - Easily shared (Wiresheep), account accessed - So we did HTTPS and noJS Backend called API with a shared secret (on a good system - some just trusted the web servers). Users never saw it, all good. Note that throughout this presentation, the sequence diagrams may be missing some steps so I can focus on the important details --- background-image: url(javascript-naive-transparent.png) # JavaScript is everywhere ??? NoJS no longer works. Javascript is calling authenticated APIs within WebApps, and cookies don't cut it. --- background-image: url(javascript-same-domain-transparent.png) # Solving it for one site --- background-image: url(javascript-cross-domain-transparent.png) class: top # But I don't want to login to every site --- background-image: url(cookie-hijack-transparent.png) # The cookie problem ??? How to steal a cookie: * Wiresheep : steal it in plain text (but please tell me your cookies are all HTTPS) * XSS : steal it via Javascript on your machine (but please tell me your cookies are HTTP-only) * Assuming the data isn't available some other way (hello, local storage) --- # Hello OAuth * Separate identity onto its own service * Or a 3rd party service * Delegate identity for finer-grained control * e.g. you can see my calendar but not my email * Each application has a unique ID and secret * Each application set of approved capabilities (scopes) .callout-image[🤝] --- background-image: url(oauth-with-shared-secret-transparent.png) --- # The problem Secrets aren't secret * Put a shared secret in the Javascript, even obscured * => you have no way to know who's using that secret * Keep the shared secret on the server * => you have no way to know who's calling your API, unless you can trust your cookie * But what if your identity server is on another domain * => either an SSO solution, or a federated login? * => how do you share a secret without sharing your secrets? .callout-image[🙊] --- # The other problem No. More. (3rd party) cookies. 🐶--(no login cookie for you)-->😈 (This breaks 3rd party identity providers on Safari today, Chrome shortly, and Firefox soon) .callout-image[🍪] --- # Zero trust and the rules of network security * We don't trust the network * Secret we receive has to pair with the secret we kept * We don't trust the client * We can't hand out a shared secret * We don't trust other servers, and neither do the browsers * We can't use 3rd party cookies .callout-image[🙅♀️] --- # The old way Vs new way * No 3rd party cookies * No refresh tokens * No trust - verify everything * Note : this may be new for web developers, but app developers have been doing this for a while .callout-image[🙅🏾♂️] --- background-image: url(oauth-with-pkce-transparent.png) ??? [Auth0 PKCE docs](https://auth0.com/docs/flows/authorization-code-flow-with-proof-key-for-code-exchange-pkce) --- # What's PKCE's secret? * There is no "public" secret * Client generates a `code_verifier` securely, and a `code_challenge` from that * Login request uses the `code_challenge` and gets an `auth code` * `auth code` is only sent to a recognised callback URL for the application requesting the login * `auth code` and `code_verifier` are sent to identity provider * `code verifier` is proof that this is the same client that originally requested Login * If code is verified, and auth code matches expectations, `access_token` is granted .callout-image[🤝✔] --- # Code sample (MSAL to AD B2C) Other libraries and identity providers are available .callout-image[👩💻] ```javascript const msalConfig = { auth: { clientId: "enter_client_id_for_😈", authority: "🌍.B2C", knownAuthorities: [], cloudDiscoveryMetadata: "", }, // !important - no secrets here // Put other behavioural settings here } const msalInstance = new PublicClientApplication(msalConfig); msalInstance.ssoSilent({redirectUrl: https://...}); if (!loggedIn) { msalInstance.loginRedirect({redirectUrl: https://...}); } ``` --- # New rules * No refresh tokens * So nothing to store in local storage * Let the library/IdP manage the session * Do silent login to refresh - browser will hit IdP * Application ID and callback URLs are public * Use least privilege * Limit application scopes * Keep callback URL simple, pass access token back to SPA as soon as possible * Store access token securely, as always .callout-image[🔐] --- # Summary * The web ways of old aren't secure enough for SPAs * The old OAuth path has some security holes * The old OAuth path doesn't work with Safari or Chrome * If you need login for your SPA, USE PKCE. .callout-image[⏪] --- # Questions .callout-image[⁉]