Slide 1

Slide 1 text

OAuth 2.1 & Beyond Dominick Baier @leastprivilege

Slide 2

Slide 2 text

2 @leastprivilege Me • Independent Consultant – Specializing on Application Security Architectures & Security Protocols – Working with Software Development Teams (ISVs and in-house) • Co-Creator of IdentityServer & IdentityModel OSS Project – Certified OpenID Connect & OAuth 2.0 Engine for ASP.NET Core – https://identityserver.io • Co-Creator of PolicyServer – Authorization for modern Applications – https://policyserver.io email [email protected] blog https://leastprivilege.com twitter @leastprivilege slides https://speakerdeck.com/leastprivilege

Slide 3

Slide 3 text

3 @leastprivilege High Security OAuth FAPI https://fapi.openid.net

Slide 4

Slide 4 text

4 @leastprivilege Agenda • OAuth security best current practices & OAuth 2.1 – common attacks, countermeasures & recommendations • Advanced OAuth – tokens, scopes, resources and audience restrictions – rich authorization requests (RAR) – JWT secured authorization requests & request objects (JAR) – pushed authorization requests (PAR) – strong client authentication – proof-of-possession access tokens – delegation, impersonation and token exchange – "OAuth 3.0" outlook https://identityserver.io/training/advanced-oauth.html

Slide 5

Slide 5 text

5 @leastprivilege Agenda • OAuth 2.0 to 2.1 diff • Beyond – improving front-channel security – improving back-channel security – proof-of-possession access tokens

Slide 6

Slide 6 text

6 @leastprivilege Relevant Documents OAuth 2.0 Security Best Current Practice https://tools.ietf.org/html/draft-ietf-oauth- security-topics/ OAuth 2.0 Threat Model & Security Considerations https://tools.ietf.org/html/rfc6819 JSON Web Token Best Current Practices https://tools.ietf.org/html/rfc8725 OAuth 2.0 for native Applications https://tools.ietf.org/html/rfc8252 OAuth 2.0 for Browser-Based Applications https://tools.ietf.org/wg/oauth/draft-ietf-oauth-browser-based-apps/ JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens https://tools.ietf.org/wg/oauth/draft-ietf-oauth- access-token-jwt/

Slide 7

Slide 7 text

7 @leastprivilege OAuth 2.1 • RFC 6749 + RFC 6750 + various BCPs == OAuth 2.1 – https://tools.ietf.org/html/draft-parecki-oauth-v2-1 • Most important changes – omission of implicit grant – omission of password grant – PKCE must be used with the authorization code grant – redirect URIs must be compared using exact string matching – refresh tokens must be either sender-constrained or one-time use only – bearer tokens usage omits token in the query string

Slide 8

Slide 8 text

8 @leastprivilege Improved Front-Channel with RAR, JAR & PAR

Slide 9

Slide 9 text

9 @leastprivilege Rich Authorization Requests (RAR) • scope parameter is typically coarse grained – some implementations use custom structured format, e.g. transaction:id • RAR introduces new authorization_detail parameter – "allow client to make a payment of 45€" – "allow client to read folder X, and write file Y" • Outcome of decision can be used to restrict access token to certain action(s) – typically combined with consent https://tools.ietf.org/html/draft-ietf-oauth-rar

Slide 10

Slide 10 text

10 @leastprivilege Example [ { "type": "customer_information", "locations": [ "https://example.com/customers" ], "actions": [ "read" ], "datatypes": [ "contacts" ] }, { "type": "customer_images", "locations": [ "https://example.com/images" ], "actions": [ "print" ], "datatypes": [ "photos" ], "identifier": "1" } ] represents the kinds of data being requested from the resource A string identifier indicating a specific resource available at the API schema

Slide 11

Slide 11 text

11 @leastprivilege Example (2) { "type": "https://scheme.example.org/files", "locations": [ "https://example.com/files" ], "permissions": [ { "path": "/myfiles/A", "access": [ "read" ] }, { "path": "/myfiles/A/X", "access": [ "read", "write" ] } ] }

Slide 12

Slide 12 text

12 @leastprivilege Example Authorization Request GET /authorize?response_type=code &client_id=s6BhdRkqt3 &state=af0ifjsldkj &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &code_challenge_method=S256 &code_challenge=K2-ltc83acc4h0c9w6ESC_rEMTJ3bwc-uCHaoeK1t8U &authorization_details=%5B%7B%22type%22%3A%22account%5Finformati on%22%2C%22actions%22%3A%5B%22list%5Faccounts%22%2C%22read%5Fbal ances%22%2C%22read%5Ftransactions%22%5D%2C%22locations%22%3A%5B% 22https%3A%2F%2Fexample%2Ecom%2Faccounts%22%5D%7D%5D HTTP/1.1 Host: server.example.com

Slide 13

Slide 13 text

13 @leastprivilege Access Token • Included either in JWT or introspection response { "iss": "http://as.com", "aud": "https://example.com/images" "iat": 1588420700 "exp": 1588420800, "sub": "123", "client_id": "456" "authorization_details": [ … ] }

Slide 14

Slide 14 text

14 @leastprivilege JWT Secured Authorization Requests (JAR) • Allows passing authorization parameters as a JSON Web Token – signed for integrity and client authentication – encrypted for confidentiality • Mitigates several known attacks – redirection URI re-writing – mix-up attacks https://tools.ietf.org/html/draft-ietf-oauth-jwsreq

Slide 15

Slide 15 text

15 @leastprivilege JWT Secured Authorization Request GET /authorize?client_id=client&response_type=code&redirect_uri=https://myapp.com/cb &state=abc&code_challenge=def&scope=openid customer.api GET /authorize?client_id=client&request= eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ewogICAgImlzcyI6ICJzNkJoZF JrcXQzIiwKICAgIC.JhdWQiOiAiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iL Aog ICAgInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsCiAgICAiY2xpZW 50X2 lkIjogInM2QmhkUmtxdDMiLAogICAgInJlZGlyZWN0X3VyaSI6ICJodHR wczovL2Ns aWVudC5leGFtcGxlLm9yZy9jYiIsCiAgICAic2NvcGUiOiAib3Blbml kIiwKICAgIC JzdGF0ZSI6ICJhZjBpZmpzbGRraiI.sCiAgICAibm9uY2UiOiAibi0wU zZfV3pBMk1q IiwKICAgICJtYXhfYWdlIjogODY0MDAKfQ.Nsxa_18VUElVaPjqW _ToI1yrEJ67BgK b5xsuZRVqzGkfKrOIX7BCx0biSxYGmjK9KJPctH1OC0iQJwXu5Y

Slide 16

Slide 16 text

16 @leastprivilege Request Object { "typ": "oauth.authz.req+jwt", "alg": "RS256", "kid": "1" }. { "iss": "client", "aud": "https://authorizationserver.com", "response_type": "code", "client_id": "client", "redirect_uri": "https://myapp.com/cb", "scope": "openid customer.api", "state": "abc", "code_challenge": "def" }. [Signature]

Slide 17

Slide 17 text

17 @leastprivilege Enabling JAR in IdentityServer new Client { ClientId = "jar.client", AllowedGrantTypes = GrantTypes.Code, RequireRequestObject = true, ClientSecrets = { new Secret { Type = IdentityServerConstants.SecretTypes.JsonWebKey, Value = "{'e':'AQAB','kid':'Zz..EA','kty':'RSA','n':'wW..Kw'}" } } }

Slide 18

Slide 18 text

18 @leastprivilege Pushed Authorization Requests (PAR) • Complements JAR • Provides interoperable endpoint to push authorization request parameters – in exchange for a request_uri – allows for authentication for confidential clients – manages entropy and lifetime https://tools.ietf.org/html/draft-ietf-oauth-par

Slide 19

Slide 19 text

19 @leastprivilege Pushed Authorization Request & Response POST /par client_id=client& response_type=code& redirect_uri=https://myapp.com/cb& state=abc& code_challenge=def& authorization_details={…} HTTP/1.1 201 Created { "request_uri": "urn:par:bwc4JK-cc191e-Y1LTC2", "expires_in": 90 }

Slide 20

Slide 20 text

20 @leastprivilege Authorization Request using request_uri • Front-channel only used to transmit reference to authenticated and validated request object GET /authorize?client_id=client& request_uri=urn:par:bwc4JK-cc191e-Y1LTC2

Slide 21

Slide 21 text

21 @leastprivilege Summary • RAR allows for structured resource access – replaces scope • JAR allows for signed, authenticated authorization requests – also encrypted if needed – also allows transmitting additional (trusted) data • PAR provides endpoint to push authorization request – removes all data from front-channel

Slide 22

Slide 22 text

22 @leastprivilege Improved Back-Channel with stronger Authentication and PoP

Slide 23

Slide 23 text

23 @leastprivilege Shared Secrets POST /token grant_type=client_credentials client_id=client client_secret=secret

Slide 24

Slide 24 text

24 @leastprivilege Recommendations • Use client secrets based on asymmetric keys – AS does not need to store any secret (only the public key) – secret not exposed over the wire • private_key_jwt – https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication – https://tools.ietf.org/html/rfc7523 • Mutual TLS – https://tools.ietf.org/html/rfc8705

Slide 25

Slide 25 text

25 @leastprivilege private_key_jwt • Client creates a JWT and signs it with its private key – can add additional custom claims Claim Description iss must contain the client ID of the OAuth client sub must contain the client ID of the OAuth client aud identifies the AS, e.g. URL of token endpoint or issuer name jti unique identifier for the token, which can be used to prevent reuse of the token exp expiration time of JWT iat time at which the jwt was issued (optional)

Slide 26

Slide 26 text

26 @leastprivilege Sending a private_key_jwt • Assertion type – urn:ietf:params:oauth:client-assertion-type:jwt-bearer POST /token grant_type=client_credentials client_assertion_type=urn:… client_assertion=

Slide 27

Slide 27 text

27 @leastprivilege Creating a Client JWT private static string CreateClientToken(SigningCredentials credential, string clientId, string audience) { var now = DateTime.UtcNow; var token = new JwtSecurityToken( clientId, audience, new List() { new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()), new Claim(JwtClaimTypes.Subject, clientId), new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64) }, now, now.AddMinutes(1), credential ); var tokenHandler = new JwtSecurityTokenHandler(); return tokenHandler.WriteToken(token); }

Slide 28

Slide 28 text

28 @leastprivilege Sending a private_key_jwt static async Task RequestTokenAsync(SigningCredentials credential) { var client = new HttpClient(); var clientToken = CreateClientToken(credential, "client.jwt", disco.TokenEndpoint); var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, Scope = "api1", ClientAssertion = { Type = OidcConstants.ClientAssertionTypes.JwtBearer, Value = clientToken } }); if (response.IsError) throw new Exception(response.Error); return response; }

Slide 29

Slide 29 text

29 @leastprivilege private_key_jwt and ASP.NET Core Clients public class OidcEvents : OpenIdConnectEvents { private readonly ClientTokenService _clientTokenService; public OidcEvents(ClientTokenService clientTokenService) { _clientTokenService = clientTokenService; } public override Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context) { context.TokenEndpointRequest.ClientAssertionType = OidcConstants.ClientAssertionTypes.JwtBearer; context.TokenEndpointRequest.ClientAssertion = _clientTokenService.CreateClientToken(); return Task.CompletedTask; } }

Slide 30

Slide 30 text

30 @leastprivilege Enabling private_key_jwt in IdentityServer • Wires up parser and validator • Adds replay cache based on IDistributedCache – can be replaced var builder = services.AddIdentityServer() .AddJwtBearerClientAuthentication();

Slide 31

Slide 31 text

31 @leastprivilege Client Definition in IdentityServer new Client { ClientId = "private.key.jwt", AllowedGrantTypes = GrantTypes.CodeAndClientCredentials, RequirePkce = true, ClientSecrets = { new Secret { Type = IdentityServerConstants.SecretTypes.X509CertificateBase64, Value = "M..A=" }, new Secret { Type = IdentityServerConstants.SecretTypes.JsonWebKey, Value = "{'e':'AQAB','kid':'Zz..EA','kty':'RSA','n':'wW..Kw'}" } } }

Slide 32

Slide 32 text

32 @leastprivilege "Proof-of-Possession" History • Most wanted feature since initial OAuth 2.0 spec – only bearer tokens were specified • Several attempts to introduce PoP at the application layer – required a lot of application layer crypto (e.g. signing of HTTP requests) – some unsolved problems (e.g. streaming) • ...then at the transport layer – HTTP token binding (deprecated)

Slide 33

Slide 33 text

33 @leastprivilege Weakness of Bearer Tokens • Bearer tokens are not bound to the client – attacker can replay leaked tokens Authorization: Bearer

Slide 34

Slide 34 text

34 @leastprivilege Proof of Possession using MTLS • OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens – https://tools.ietf.org/html/rfc8705 • Covers both authentication and Proof-of-Possession

Slide 35

Slide 35 text

35 @leastprivilege Mutual TLS TLS Tunnel server certificate client certificate • authentication • proving to know a secret (private key) • validate chain of trust (optional) • negotiate key material • sign & encrypt traffic

Slide 36

Slide 36 text

36 @leastprivilege Sender Constrained Access Tokens w/ MTLS { "iss": "https://issuer", "exp": 1340819380, "nbf": 1340818761, "client_id": "182jmm199", "scope": "api1", "cnf": { "x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2" } }

Slide 37

Slide 37 text

37 @leastprivilege Creating an X.509 Client Certificate static X509Certificate2 CreateClientCertificate(string name) { var distinguishedName = new X500DistinguishedName($"CN={name}"); using (var rsa = RSA.Create(2048)) { var request = new CertificateRequest( distinguishedName, rsa, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add( new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); request.CertificateExtensions.Add( new X509EnhancedKeyUsageExtension( new OidCollection { new Oid("1.3.6.1.5.5.7.3.2") }, false)); return request.CreateSelfSigned( new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(10))); } }

Slide 38

Slide 38 text

38 @leastprivilege Setting a Client Certificate static SocketsHttpHandler GetHandler(X509Certificate2 certificate) { var handler = new SocketsHttpHandler(); handler.SslOptions.ClientCertificates = new X509CertificateCollection { certificate }; return handler; }

Slide 39

Slide 39 text

39 @leastprivilege Calling the Token Endpoint static async Task RequestTokenAsync() { var client = new HttpClient(GetHandler()); var disco = await client.GetDiscoveryDocumentAsync("https://identityserver.local"); var endpoint = disco .TryGetValue(OidcConstants.Discovery.MtlsEndpointAliases) .Value(OidcConstants.Discovery.TokenEndpoint) .ToString(); var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = endpoint, ClientId = "mtls", Scope = "api1" }); return response; }

Slide 40

Slide 40 text

40 @leastprivilege MTLS Endpoints • Sub-path • Sub-domain • Separate domain https://identityserver.io/connect/mtls/* https://mtls.identityserver.io/* https://identityserver-mtls.io/*

Slide 41

Slide 41 text

41 @leastprivilege Server Metadata • Endpoint aliases specify the URLs of the MTLS versions of the standard endpoints

Slide 42

Slide 42 text

42 @leastprivilege Choice of Web Server / Proxy • IIS – supports path-based endpoints – PKI method only • Azure – supports path-based endpoints (but only via exclusion paths) • Kestrel – does not support any isolation • Nginx – supports domain/sub-domain isolation – supports PKI and self-signed

Slide 43

Slide 43 text

43 @leastprivilege Example: Sub-domains with Nginx server { listen 443 ssl; server_name identityserver.io; location / { proxy_pass http://localhost:5000; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl; server_name mtls.identityserver.local; ssl_verify_client optional_no_ca; location /connect { proxy_pass http://localhost:5000; proxy_set_header X-SSL-CERT $ssl_client_escaped_cert; } } ASP.NET Core self-signed forward certificate on header

Slide 44

Slide 44 text

44 @leastprivilege Reading Certificate from Header public void ConfigureServices(IServiceCollection services) { services.AddCertificateForwarding(options => { options.CertificateHeader = "X-SSL-CERT"; options.HeaderConverter = (headerValue) => { X509Certificate2 clientCertificate = null; if(!string.IsNullOrWhiteSpace(headerValue)) { byte[] bytes = Encoding.UTF8.GetBytes(Uri.UnescapeDataString(headerValue)); clientCertificate = new X509Certificate2(bytes); } return clientCertificate; }; }); } public void Configure(IApplicationBuilder app) { app.UseCertificateForwarding(); }

Slide 45

Slide 45 text

45 @leastprivilege Certificate Authentication Handler public void ConfigureServices(IServiceCollection services) { services.AddAuthentication() .AddCertificate(options => { options.AllowedCertificateTypes = CertificateTypes.All; options.RevocationMode = X509RevocationMode.NoCheck; }); }

Slide 46

Slide 46 text

46 @leastprivilege Enabling MTLS in IdentityServer var builder = services.AddIdentityServer(options => { options.MutualTls.Enabled = true; // sets "mtls" sub-domain options.MutualTls.DomainName = "mtls"; }); builder.AddMutualTlsSecretValidators();

Slide 47

Slide 47 text

47 @leastprivilege Verifying Access Token Ownership at Resource • Resource server needs similar setup – accept/validate client certificates – forwarding of certificates to application host • After normal access token validation – compare certificate thumbprint of TLS channel with cnf claim in access token – if they match, caller can prove that it has access to the same key material • proof-of-possession

Slide 48

Slide 48 text

48 @leastprivilege Pipeline Overview public void ConfigureServices(IServiceCollection services) { services.AddAuthentication("jwt") .AddJwtBearer("jwt", options => { … }) .AddCertificate(options => { … }); services.AddCertificateForwarding(…) } public void Configure(IApplicationBuilder app) { app.UseForwardedHeaders(…) app.UseCertificateForwarding(); app.UseAuthentication(); app.UseConfirmationValidation(…) app.UseAuthorization(); }

Slide 49

Slide 49 text

49 @leastprivilege Summary • Asymmetric keys are easier to manage and secure than shared secrets • Private key JWT – allows securing both front- and back-channel with single key pair • TLS client certificates – allows binding access token to client – even for public clients