A JWT profile for OAuth2 Access Tokens vittorio@auth0.com @vibronet
TL;DR OAuth2 doesn’t mandate a format for access tokens Many AS products are issuing access tokens in JWT format… …but everyone does it slightly differently A JWT profile for ATs would make it possible to create vendor-agnostic SDKs for API make it easier for developers to move their code across vendors Aspects to specify Mandatory claims for validation Metadata docs for validation info Claims carrying authorization information Optional claims in common use
Agenda Analysis of JWT use in ATs issued by production services today Draft proposal of a JWT as AT profile
ATs as JWTs in the wild
Why are providers using JWT for ATs Format based validation is a well proven approach Performant No extra network traffic No bottlenecks Resilient No throttling Not sensitive to network issues Not sensitive to AS outages Easy troubleshooting Easy setup/ubiquitous validation SDKs Easy extensibility (custom claims etc)
Products considered Auth0 Azure AD Ping Identity IdentityServer OKTA AWS Thanks to Daniel Dobalian, Brian Campbell, Dominick Bauer, Karl Guinness for providing sample ATs in JWT format
Common traits Nearly everyone use id_token/introspection infrastructural claims Nearly everyone use OIDC discover to advertise issuer, signing keys Different claim types for scopes Wide gamut of additional identity, client & auth info, authorization claims
Claims idtoken Auth0 Azure AD PingIdentity IdentityServer AWS OKTA Profile Validation iss aud exp iat nonce auth_time nbf jti [aud] iad Identity sub lots <any> name preferred_username oid ipaddr unique_name email uid [sub] username cid Authorization N/A scope roles scp groups memberOf ?roles, groups Context/misc azp acr amr gty aio app_displayname appid idp tid uti ver xms_tcdt --- azpacr idpid client_id token_use ?idp ?azpacr Idtoken validation: https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
A profile for ATs as JWT
ATs as JWT Proposal - summary ATs are JWTs signed with RS256 (or any other asymm algo) Strongly typed to ensure they are not interchangeable w it_tokens typ=access_token+jwt Validation coordinates (issuer, signature check keys…) published via OIDC discovery and/or AS metadata 8414 (more or less) same validation rules as id_token in OIDC Core But ensuring strong type check, disallowing nonce, etc Mandatory + optional claims layout Thanks to Brian Campbell, Filip Skokan for early feedback and valuable insights
ATs as JWT – claims layout Functional area claim type origin Validation iss as for id_token in OIDC core, introspection exp, iat auth_time aud resource indicators jti 7519 JWT Identity sub <oidc profile claims> as for id_token in OIDC core Authorization scope from token_exchange groups, roles, entitlements 7643 (SCIM) Context/misc client_id acr, amr azpacr <new?> idp <new?> OIDC federation? Bold == mandatory
Discussion
Auth0 { { "iss": "https://flosser.auth0.com/", "sub": "auth0|5ba552d674717b20e52f56cd", "aud": [ "https://flosser.com/api/", "https://flosser.auth0.com/userinfo" ], "iat": 1544558774, "exp": 1544645174, "azp": "xHMI55zgwY0PnaztfSQflbFAwxxHUM8_", "scope": "openid profile email read:reports read:appointments offline_access" } { "iss": "https://flosser.auth0.com/", "sub": "uNkUAIDPx1zgXfuodmR7CNHutYWPZ96L@clients", "aud": "https://flosser.auth0.com/api/v2/", "iat": 1537798395, "exp": 1537884795, "azp": "uNkUAIDPx1zgXfuodmR7CNHutYWPZ96L", "scope": "read:users", "gty": "client-credentials" } Validation Identity Authorization Context
Azure AD { "aud": "ef1da9d4-ff77-4c3e-a005-840c3f830745", "iss": "https://sts.windows.net/fa15d692-e9c7-4460-a743-29f29522229/", "iat": 1537233106, "nbf": 1537233106, "exp": 1537237006, "acr": "1", "aio": "AXQAi/8IAAAAFm+E/QTG+gFnVxLjWdw8K+61AGrSOuMMF6ebaMj7XO3IbmD3fGmrOyD+NvZyGn2VaT/kDKXw4MIhrgGVq6Bn8wLXoT1LkIZ+FzQVkJPPLQOV4KcXqSlCVPDS/DiCDgE222TImMvWNaEMaUOTsIGvTQ==", "amr": [ "wia" ], "appid": "75dbe77f-10a3-4e59-85fd-8c127544f17c", "appidacr": "0", "email": "AbeLi@microsoft.com", "family_name": "Lincoln", "given_name": "Abe (MSFT)", "idp": "https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd0122247/", "ipaddr": "222.222.222.22", "name": "abeli", "oid": "02223b6b-aa1d-42d4-9ec0-1b2bb9194438", "rh": "I", "scp": "user_impersonation", "sub": "l3_roISQU222bULS9yi2k0XpqpOiMz5H3ZACo1GeXA", "tid": "fa15d692-e9c7-4460-a743-29f2956fd429", "unique_name": "abeli@microsoft.com", "uti": "FVsGxYXI30-TuikuuUoFAA", "ver": "1.0" } Azure AD { "aud": "6e74172b-be56-4843-9ff4-e66a39bb12e3", "iss": "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0", "iat": 1537231048, "nbf": 1537231048, "exp": 1537234948, "aio": "AXQAi/8IAAAAtAaZLo3ChMif6KOnttRB7eBq4/DccQzjcJGxPYy/C3jDaNGxXd6wNIIVGRghNRnwJ1lOcAnNZcjvkoyrFxCttv33140RioOFJ4bCCGVuoCag1uOTT22222gHwLPYQ/uf79QX+0KIijdrmp69RctzmQ==", "azp": "6e74172b-be56-4843-9ff4-e66a39bb12e3", "azpacr": "0", "name": "Abe Lincoln", "oid": "690222be-ff1a-4d56-abd1-7e4f7d38e474", "preferred_username": "abeli@microsoft.com", "rh": "I", "scp": "access_as_user", "sub": "HKZpfaHyWadeOouYlitjrI-KffTm222X5rrV3xDqfKQ", "tid": "72f988bf-86f1-41af-91ab-2d7cd011db47", "uti": "fqiBqXLPj0eQa82S-IYFAA", "ver": "2.0" } { "aud": "https://graph.microsoft.com", "iss": "https://sts.windows.net/26039cce-489d-4002-8293-5b0c5134eacb/", "iat": 1551922140, "nbf": 1551922140, "exp": 1551965640, "aio": "42JgYNilxTPr/L1HxVu+ZvT�nmZWBQA=", "app_displayname": "TestSecVuln", "appid": "50ddfc06-811f-4fcf-85c9-e7febdfd7885", "appidacr": "1", "idp": "https://sts.windows.net/26039cce-489d-4002-8293-5b0c5134eacb/", "oid": "83820349-ca67-44e5-851b-79685d996ba2", "roles": [ "User.Read.All" ], "sub": "83820349-ca67-44e5-851b-79685d996ba2", "tid": "26039cce-489d-4002-8293-5b0c5134eacb", "uti": "XHWCcMtGeE-u_E-Dv1IOAA", "ver": "1.0", "xms_tcdt": 1467231125 }
PingIdentity { { "sub": "mdorey+adminaudit@pingidentity.com", { "idpid": "24ad9bc6-a69f-4498-a9be-258126beaa6f", "scope": "openid", "iss": "https://test-sso.connect.pingidentity.com/cdd237bb-3404-4ad4-90eb-d2e252808037", "memberOf": [ "Domain Administrators@directory", "Users@directory", "PINGONE.CLOUD.DIRECTORY.GROUP.UI.ENTITLEMENT.OKRGSKYHXRGFKJGBNOGIKRSAZZQJNYFCMKHFULMJPTFRBI" ], "exp": 1533832421, "jti": "IDa57ef23fb8909d45100882a89d29d56cfdf981fc6d7fe2c602000001651f895ea0", "client_id": "cdd237bb-3404-4ad4-90eb-d2e252808037" } { "sub": "mdorey+adminaudit@pingidentity.com", "idpid": "24ad9bc6-a69f-4498-a9be-258126beaa6f", "scope": "openid", "iss": "https://test-sso.connect.pingidentity.com/cdd237bb-3404-4ad4-90eb-d2e252808037", "memberOf": [ "Domain Administrators@directory", "Users@directory", "PINGONE.CLOUD.DIRECTORY.GROUP.UI.ENTITLEMENT.OKRGSKYHXRGFKJGBNOGIKRSAZZQJNYFCMKHFULMJPTFRBI" ], "exp": 1534518144, "jti": "ID96448e408baaed53c8210c3a3788f958096073cb4d7ffe4602000001654868ad90", "client_id": "cdd237bb-3404-4ad4-90eb-d2e252808037" } { "sub": "mdorey+adminaudit@pingidentity.com", "idpid": "24ad9bc6-a69f-4498-a9be-258126beaa6f", "scope": "openid", "iss": "https://test-sso.connect.pingidentity.com/cdd237bb-3404-4ad4-90eb-d2e252808037", "memberOf": [ "Domain Administrators@directory", "Users@directory", "PINGONE.CLOUD.DIRECTORY.GROUP.UI.ENTITLEMENT.OKRGSKYHXRGFKJGBNOGIKRSAZZQJNYFCMKHFULMJPTFRBI" ], "exp": 1534438896, "jti": "ID9cb74fbe81592cadd9bb3d3a7587799e73b92714ca64ffef020000016543af72e8", "client_id": "cdd237bb-3404-4ad4-90eb-d2e252808037" } { "scope": "profile sure:whatever ok:fine", "client_id": "bdc", "sub": "test", "uid": "2d425f77", "rtttl": 2147483647, "email": "test@example.com", "exp": 1551381936 } { "scope": "sure:whatever ok:fine", "client_id": "bdc", "iss": "https://pfdev.ping-eng.com", "aud": "urn:some:api", "sub": "test", "uid": "2d425f77", "rtttl": 2147483647, "email": "test@example.com", "exp": 1551382159 } { "scope": "profile sure:whatever ok:fine", "aud": "really this can be anything", "sub": "test", "uid": "2d425f77", "rtttl": 2147483647, "email": "test@example.com", "exp": 1551382365 }
IdentityServer { "nbf": 1551775904, "exp": 1551779504, "iss": "http://localhost:5000", "aud": [ "http://localhost:5000/resources", "api1" ], "client_id": "mvc.hybrid", "sub": "88421113", "auth_time": 1551775899, "idp": "local", "scope": [ "openid", "profile", "email", "api1", "offline_access" "amr": [ "pwd" ] } IdentityServer { "nbf": 1551775833, "exp": 1551779433, "iss": "http://localhost:5000", "aud": [ "http://localhost:5000/resources", "api1" ], "client_id": "client", "scope": [ ] }
OKTA { "ver": 1, "jti": "AT.0mP4JKAZX1iACIT4vbEDF7LpvDVjxypPMf0D7uX39RE", "iss": "https://okta.okta.com/oauth2/0oacqf8qaJw56czJi0g4", "aud": "https://api.example.com", "sub": "00ujmkLgagxeRrAg20g3", "iat": 1467145094, "exp": 1467148694, "cid": "nmdP1fcyvdVO11AL7ECm", "uid": "00ujmkLgagxeRrAg20g3", "scp": [ "openid", "email", "flights", "custom" ], "custom_claim": "CustomValue" }