Skip to content

Commit 5215d24

Browse files
authored
feat: implement RFC 8628 (#3912)
This patch introduces the OAuth 2.0 Device Authorization Grant to Ory Hydra. The OAuth 2.0 device authorization grant is designed for Internet-connected devices that either lack a browser to perform a user-agent-based authorization or are input constrained to the extent that requiring the user to input text in order to authenticate during the authorization flow is impractical. It enables OAuth clients on such devices (like smart TVs, media consoles, digital picture frames, and printers) to obtain user authorization to access protected resources by using a user agent on a separate device. The OAuth 2.0 Device Authorization Grant may also become relevant for AI Agent authentication flows and is generally an amazing step and innovation for this project. A very special thanks goes to @nsklikas from [Canonical](https://canonical.com), @supercairos from [shadow.tech](https://shadow.tech) and @BuzzBumbleBee. For more details, please check out the documentation (ory/docs#2026) To implement this feature, you will need to implement two additional screens in your login and consent application. A reference implementation can be found [here](https://github.com/ory/hydra-login-consent-node/blob/99ca6ad544f64110706c289dda74c7c622ec3110/src/routes/device.ts). Closes #3851 Closes #3252 Closes #3230 Closes #2416
1 parent 3f86782 commit 5215d24

File tree

143 files changed

+9226
-1176
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+9226
-1176
lines changed

.schema/config.schema.json

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,11 @@
464464
"description": "Sets the session cookie name. Use with care!",
465465
"type": "object",
466466
"properties": {
467+
"device_csrf": {
468+
"type": "string",
469+
"title": "CSRF Cookie Name",
470+
"default": "ory_hydra_device_csrf"
471+
},
467472
"login_csrf": {
468473
"type": "string",
469474
"title": "CSRF Cookie Name",
@@ -614,6 +619,14 @@
614619
"https://my-service.com/oauth2/auth"
615620
]
616621
},
622+
"device_authorization_url": {
623+
"type": "string",
624+
"description": "Overwrites the OAuth2 Device Auth URL",
625+
"format": "uri-reference",
626+
"examples": [
627+
"https://my-service.com/oauth2/device/auth"
628+
]
629+
},
617630
"client_registration_url": {
618631
"description": "Sets the OpenID Connect Dynamic Client Registration Endpoint",
619632
"type": "string",
@@ -803,6 +816,30 @@
803816
"/ui/logout"
804817
]
805818
},
819+
"device": {
820+
"type": "object",
821+
"description": "Configure URLs for the OAuth 2.0 Device Code Flow.",
822+
"properties": {
823+
"verification": {
824+
"type": "string",
825+
"description": "Sets the device user code verification endpoint. Defaults to an internal fallback URL showing an error.",
826+
"format": "uri-reference",
827+
"examples": [
828+
"https://my-logout.app/device_verification",
829+
"/ui/device_verification"
830+
]
831+
},
832+
"success": {
833+
"type": "string",
834+
"description": "Sets the post device authentication endpoint. Defaults to an internal fallback URL showing an error.",
835+
"format": "uri-reference",
836+
"examples": [
837+
"https://my-logout.app/device_done",
838+
"/ui/device_done"
839+
]
840+
}
841+
}
842+
},
806843
"error": {
807844
"type": "string",
808845
"description": "Sets the error endpoint. The error ui will be shown when an OAuth2 error occurs that which can not be sent back to the client. Defaults to an internal fallback URL showing an error.",
@@ -947,6 +984,15 @@
947984
"$ref": "#/definitions/duration"
948985
}
949986
]
987+
},
988+
"device_user_code": {
989+
"description": "Configures how long device & user codes are valid.",
990+
"default": "10m",
991+
"allOf": [
992+
{
993+
"$ref": "#/definitions/duration"
994+
}
995+
]
950996
}
951997
}
952998
},
@@ -1124,6 +1170,28 @@
11241170
}
11251171
]
11261172
},
1173+
"device_authorization": {
1174+
"type": "object",
1175+
"additionalProperties": false,
1176+
"properties": {
1177+
"token_polling_interval": {
1178+
"allOf": [
1179+
{
1180+
"$ref": "#/definitions/duration"
1181+
}
1182+
],
1183+
"default": "5s",
1184+
"description": "Configures how often a non-interactive device should poll the device token endpoint, this is a purely informational configuration and does not enforce rate-limiting.",
1185+
"examples": ["5s", "15s", "1m"]
1186+
},
1187+
"user_code_entropy": {
1188+
"type": "string",
1189+
"description": "Sets the entropy for the user codes.",
1190+
"default": "medium",
1191+
"enum": ["high", "medium", "low"]
1192+
}
1193+
}
1194+
},
11271195
"token_hook": {
11281196
"description": "Sets the token hook endpoint for all grant types. If set it will be called while providing token to customize claims.",
11291197
"examples": ["https://my-example.app/token-hook"],
@@ -1137,8 +1205,8 @@
11371205
}
11381206
]
11391207
}
1140-
}
1141-
},
1208+
}
1209+
},
11421210
"secrets": {
11431211
"type": "object",
11441212
"additionalProperties": false,
@@ -1183,7 +1251,7 @@
11831251
"examples": ["cpu"]
11841252
},
11851253
"tracing": {
1186-
"$ref": "https://raw.githubusercontent.com/ory/x/v0.0.675/otelx/config.schema.json"
1254+
"$ref": "ory://tracing-config"
11871255
},
11881256
"sqa": {
11891257
"type": "object",

client/.snapshots/TestHandler-common-case=create_clients-case=0-description=basic_dynamic_client_registration.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@
3131
"jwt_bearer_grant_access_token_lifespan": null,
3232
"refresh_token_grant_id_token_lifespan": null,
3333
"refresh_token_grant_access_token_lifespan": null,
34-
"refresh_token_grant_refresh_token_lifespan": null
34+
"refresh_token_grant_refresh_token_lifespan": null,
35+
"device_authorization_grant_id_token_lifespan": null,
36+
"device_authorization_grant_access_token_lifespan": null,
37+
"device_authorization_grant_refresh_token_lifespan": null
3538
}

client/.snapshots/TestHandler-common-case=create_clients-case=1-description=basic_admin_registration.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,8 @@
3434
"jwt_bearer_grant_access_token_lifespan": null,
3535
"refresh_token_grant_id_token_lifespan": null,
3636
"refresh_token_grant_access_token_lifespan": null,
37-
"refresh_token_grant_refresh_token_lifespan": null
37+
"refresh_token_grant_refresh_token_lifespan": null,
38+
"device_authorization_grant_id_token_lifespan": null,
39+
"device_authorization_grant_access_token_lifespan": null,
40+
"device_authorization_grant_refresh_token_lifespan": null
3841
}

client/.snapshots/TestHandler-common-case=create_clients-case=10-description=empty_ID_succeeds.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@
3131
"jwt_bearer_grant_access_token_lifespan": null,
3232
"refresh_token_grant_id_token_lifespan": null,
3333
"refresh_token_grant_access_token_lifespan": null,
34-
"refresh_token_grant_refresh_token_lifespan": null
34+
"refresh_token_grant_refresh_token_lifespan": null,
35+
"device_authorization_grant_id_token_lifespan": null,
36+
"device_authorization_grant_access_token_lifespan": null,
37+
"device_authorization_grant_refresh_token_lifespan": null
3538
}

client/.snapshots/TestHandler-common-case=create_clients-case=10-description=setting_skip_logout_consent_succeeds_for_admin_registration.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@
3232
"jwt_bearer_grant_access_token_lifespan": null,
3333
"refresh_token_grant_id_token_lifespan": null,
3434
"refresh_token_grant_access_token_lifespan": null,
35-
"refresh_token_grant_refresh_token_lifespan": null
35+
"refresh_token_grant_refresh_token_lifespan": null,
36+
"device_authorization_grant_id_token_lifespan": null,
37+
"device_authorization_grant_access_token_lifespan": null,
38+
"device_authorization_grant_refresh_token_lifespan": null
3639
}

client/.snapshots/TestHandler-common-case=create_clients-case=12-description=empty_ID_succeeds.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@
3232
"jwt_bearer_grant_access_token_lifespan": null,
3333
"refresh_token_grant_id_token_lifespan": null,
3434
"refresh_token_grant_access_token_lifespan": null,
35-
"refresh_token_grant_refresh_token_lifespan": null
35+
"refresh_token_grant_refresh_token_lifespan": null,
36+
"device_authorization_grant_id_token_lifespan": null,
37+
"device_authorization_grant_access_token_lifespan": null,
38+
"device_authorization_grant_refresh_token_lifespan": null
3639
}

client/.snapshots/TestHandler-common-case=create_clients-case=2-description=empty_ID_succeeds.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,8 @@
3030
"jwt_bearer_grant_access_token_lifespan": null,
3131
"refresh_token_grant_id_token_lifespan": null,
3232
"refresh_token_grant_access_token_lifespan": null,
33-
"refresh_token_grant_refresh_token_lifespan": null
33+
"refresh_token_grant_refresh_token_lifespan": null,
34+
"device_authorization_grant_id_token_lifespan": null,
35+
"device_authorization_grant_access_token_lifespan": null,
36+
"device_authorization_grant_refresh_token_lifespan": null
3437
}

client/.snapshots/TestHandler-common-case=create_clients-case=4-description=non-uuid_works.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,8 @@
3434
"jwt_bearer_grant_access_token_lifespan": null,
3535
"refresh_token_grant_id_token_lifespan": null,
3636
"refresh_token_grant_access_token_lifespan": null,
37-
"refresh_token_grant_refresh_token_lifespan": null
37+
"refresh_token_grant_refresh_token_lifespan": null,
38+
"device_authorization_grant_id_token_lifespan": null,
39+
"device_authorization_grant_access_token_lifespan": null,
40+
"device_authorization_grant_refresh_token_lifespan": null
3841
}

client/.snapshots/TestHandler-common-case=create_clients-case=5-description=setting_client_id_as_uuid_works.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,8 @@
3434
"jwt_bearer_grant_access_token_lifespan": null,
3535
"refresh_token_grant_id_token_lifespan": null,
3636
"refresh_token_grant_access_token_lifespan": null,
37-
"refresh_token_grant_refresh_token_lifespan": null
37+
"refresh_token_grant_refresh_token_lifespan": null,
38+
"device_authorization_grant_id_token_lifespan": null,
39+
"device_authorization_grant_access_token_lifespan": null,
40+
"device_authorization_grant_refresh_token_lifespan": null
3841
}

client/.snapshots/TestHandler-common-case=create_clients-case=7-description=setting_skip_consent_suceeds_for_admin_registration.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@
3131
"jwt_bearer_grant_access_token_lifespan": null,
3232
"refresh_token_grant_id_token_lifespan": null,
3333
"refresh_token_grant_access_token_lifespan": null,
34-
"refresh_token_grant_refresh_token_lifespan": null
34+
"refresh_token_grant_refresh_token_lifespan": null,
35+
"device_authorization_grant_id_token_lifespan": null,
36+
"device_authorization_grant_access_token_lifespan": null,
37+
"device_authorization_grant_refresh_token_lifespan": null
3538
}

0 commit comments

Comments
 (0)