Skip to content

Commit 84d5dd9

Browse files
Specify Pre-Authenticated URL
2 parents 6ef6a21 + d96def2 commit 84d5dd9

4 files changed

Lines changed: 362 additions & 93 deletions

File tree

docs/specs/dpop.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Demonstrating Proof of Possession (DPoP)
2+
3+
Authgear supports DPoP which was specified in [rfc9449](https://datatracker.ietf.org/doc/html/rfc9449).
4+
5+
The following tokens will be bound to a DPoP private key if the `DPoP` header was provided in the request which issued the token:
6+
7+
- Refresh Token
8+
- Device Secret
9+
10+
The following tokens will be bound to a DPoP private key if the `dpop_jkt` query parameter was provided in the authorization request which issued th token:
11+
12+
- Authorization Code

docs/specs/oidc-native-sso.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
- [OIDC Native SSO](#oidc-native-sso)
2+
* [Key points to note](#key-points-to-note)
3+
* [Changes on Client](#changes-on-client)
4+
* [Changes on OfflineGrant](#changes-on-offlinegrant)
5+
* [Changes on Authorization Endpoint](#changes-on-authorization-endpoint)
6+
* [Changes on Token Endpoint](#changes-on-token-endpoint)
7+
+ [`grant_type=authorization_code`](#grant_typeauthorization_code)
8+
+ [`grant_type=urn:authgear:params:oauth:grant-type:biometric-request`](#grant_typeurnauthgearparamsoauthgrant-typebiometric-request)
9+
+ [`grant_type=refresh_token`](#grant_typerefresh_token)
10+
+ [`grant_type=urn:authgear:params:oauth:grant-type:app2app`](#grant_typeurnauthgearparamsoauthgrant-typeapp2app)
11+
+ [`grant_type=urn:ietf:params:oauth:grant-type:token-exchange`](#grant_typeurnietfparamsoauthgrant-typetoken-exchange)
12+
+ [When issuing ID tokens](#when-issuing-id-tokens)
13+
* [Changes on Admin API](#changes-on-admin-api)
14+
* [Changes on SDK](#changes-on-sdk)
15+
+ [Recipe: Two applications written by the same vendor](#recipe-two-applications-written-by-the-same-vendor)
16+
+ [Recipe: A application opening webapps with custom webview](#recipe-a-application-opening-webapps-with-custom-webview)
17+
* [Caveats](#caveats)
18+
* [Security Considerations](#security-considerations)
19+
+ [Binding Tokens To Device](#binding-tokens-to-device)
20+
21+
# OIDC Native SSO
22+
23+
This document specifies the implementation of [OIDC Native SSO](https://openid.net/specs/openid-connect-native-sso-1_0.html).
24+
25+
## Key points to note
26+
27+
- A OfflineGrant used to has only one set of client-specific information, like `client_id` and `refresh_token`. Now, a OfflineGrant can have multiple refresh tokens.
28+
- Since there is only one OfflineGrant, the apps sharing user authentication with Native SSO does not share refresh token. But the underlying session is shared. Thus signing out in one app will sign out all apps. This is by design. See [App2app](./app2app.md) if you want the apps have independent sessions.
29+
- Native SSO is done through the Token Endpoint, thus it requires no user interaction.
30+
- Existing OfflineGrant cannot perform Native SSO. It is because `device_sso` is not in the `scope`. Sign in again to obtain a Native SSO OfflineGrant to perform Native SSO.
31+
32+
## Changes on Client
33+
34+
- Add `x_device_sso_enabled: boolean`.
35+
- `scope=device_sso` is allowed if `x_device_sso_enabled=true`.
36+
37+
> Do we need to add `x_device_sso_key: string` to designate which group of clients can perform Native SSO?
38+
> Only clients with `x_device_sso_enabled=true` AND the same value of `x_device_sso_key` can perform Native SSO with each other.
39+
> This seems very advanced to me.
40+
41+
## Changes on OfflineGrant
42+
43+
- The following fields become client-specific
44+
- `ClientID`
45+
- `AuthorizationID`
46+
- `CreatedAt`
47+
- `Scopes`
48+
- `TokenHash`
49+
- Add a new field `RefreshTokens`. It will store all of the above imformation per refresh token.
50+
- Add a new field `DeviceSecretHash`. It is the hex of SHA256 of `device_secret`.
51+
- All new offline grants will use the new `RefreshTokens` to store information about refresh tokens.
52+
53+
## Changes on Authorization Endpoint
54+
55+
- Allow `scope=device_sso` if the client has `x_device_sso_enabled=true`.
56+
57+
## Changes on Token Endpoint
58+
59+
### `grant_type=authorization_code`
60+
61+
- If `authorization_code.scope=device_sso` and `device_secret` is present and it is valid, a new `refresh_token` is added to Native SSO offline grant.
62+
- If `authorization_code.scope=device_sso` and `device_secret` is absent or it is invalid, a new Native SSO offline grant is created.
63+
64+
### `grant_type=refresh_token`
65+
66+
- If `refresh_token.scope=device_sso` and `device_secret` is present and it is valid, nothing to do.
67+
- If `refresh_token.scope=device_sso` and `device_secret` is absent or it is invalid, a new `device_secret` is generated. `DeviceSecretHash` is updated.
68+
69+
### `grant_type=urn:authgear:params:oauth:grant-type:biometric-request`
70+
71+
- Support a new parameter `scope`.
72+
- Allow `scope=device_sso` if the client has `x_device_sso_enabled=true`.
73+
- If `scope=device_sso` and `device_secret` is present and it is valid, a new `refresh_token` is added to Native SSO offline grant.
74+
- If `scope=device_sso` and `device_secret` is absent or it is invalid, a new Native SSO offline grant is created.
75+
76+
### `grant_type=urn:authgear:params:oauth:grant-type:app2app`
77+
78+
Native SSO has no direct impact on app2app.
79+
80+
### `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`
81+
82+
- Validate `scope=device_sso`. Return `error=invalid_request` otherwise. This is because this is the only Token Change flow we support.
83+
- Validate `audience` is the origin of the endpoint.
84+
- Validate `subject_token` is a valid ID token issued to the first app. An expired ID token is still valid. (4.3 Point 2)
85+
- Validate `subject_token_type` is `urn:ietf:params:oauth:token-type:id_token`.
86+
- Validate `actor_token` is a valid `device_secret`. (4.3 Point 1)
87+
- Validate `actor_token_type` is `urn:x-oath:params:oauth:token-type:device-secret`.
88+
- Validate `requested_token_type` is absent.
89+
- Validate `subject_token.ds_hash` is the hex of SHA256 of `actor_token`. (4.3 Point 3)
90+
- Validate `subject_token.sid` is pointing to a valid session. (4.3 Point 4)
91+
- Validate `client_id` and `subject_token.aud` are allowed to perform Native SSO. (4.3 Point 5)
92+
- Validate `scope` is equal or a subset of the `scope` in all refresh tokens of the Native SSO offline grant. (4.3 Point 6)
93+
94+
### When issuing ID tokens
95+
96+
- If the offline grant has `DeviceSecretHash`, set `ds_hash` in the ID token.
97+
- Keep setting `sid`.
98+
99+
## Changes on Admin API
100+
101+
- Add `clientIDs` to `Session`.
102+
- `Session.clientID` is the first client ID of a Native SSO offline grant.
103+
104+
## Changes on SDK
105+
106+
- Rename `isSSOEnabled` to `isBrowserSSOEnabled` in `ConfigureOptions`.
107+
- Add `isDeviceSSOEnabled` to `ConfigureOptions`.
108+
- If `isDeviceSSOEnabled` is true, then `device_sso` is included in authorization requests.
109+
- Add `deviceSecretStore`. It is responsible for storing `device_secret` and `id_token` in Token Response.
110+
- If `device_secret` is found in `deviceSecretStore` and `isDeviceSSOEnabled` is true, then `device_secret` is included in Token Request.
111+
- If `id_token` is present in Token Response, it is persisted into `deviceSecretStore`.
112+
- If `device_secret` is present in Token Response, it is persisted into `deviceSecretStore`.
113+
- `logout()` clears `deviceSecretStore`.
114+
- Add `checkDeviceSSOPossible(): Promise<void>`. It throws error if either `device_secret` or `id_token` is not found in `deviceSecretStore`.
115+
- Add `authenticateDeviceSSO(): Promise<UserInfo>`.
116+
- Expose `refreshToken` on Authgear.
117+
118+
Future works
119+
- Add `IOSAppGroupDeviceSecretStorage`.
120+
- Add `AndroidAccountManagerDeviceSecretStorage`.
121+
122+
### Recipe: Two applications written by the same vendor
123+
124+
> Recipe requires Future works to be done first.
125+
126+
1. Configure both apps to use `IOSAppGroupDeviceSecretStorage` and `AndroidAccountManagerDeviceSecretStorage`.
127+
2. Configure both apps to set `isDeviceSSOEnabled` to true.
128+
3. Sign in normally in App 1.
129+
4. In App 2, call `checkDeviceSSOPossible()`. It returns normally.
130+
5. In App 2, if `checkDeviceSSOPossible()` returns normally, call `authenticateDeviceSSO()`.
131+
6. In App 2, the end-user is authenticated. No user interaction is involved.
132+
133+
### Recipe: A application opening webapps with custom webview
134+
135+
1. Configure the app to set `isDeviceSSOEnabled` to true.
136+
2. Sign in normally.
137+
3. To open a webapp, do the following
138+
4. Construct a new Container with `tokenStorage` set to TransientStorage, and `isDeviceSSOEnabled` to true. The purpose of this is to prevent this Container from messing with the original Container. The new Container is now unauthenticated (due to TransientStorage) but has `device_secret` (due to `isDeviceSSOEnabled` being true).
139+
5. Call `authenticateDeviceSSO()`
140+
6. Inject `refreshToken` of the new Container into the custom webview. This requires knowledge on the implementation details of the Web SDK.
141+
7. The Container of the Web SDK considered itself as authenticated due to the injected refresh token.
142+
143+
## Caveats
144+
145+
- The iOS keychain will not be cleaned up even all apps in the app group were removed.
146+
- Developer is required to provide the `accountType` to initialize the store. Applications must belong to the given app group or the SDK might malfunction. Developer must also define a `<account-authenticator>` resource with the same `accountType`, and a `<service>` which uses the defined account authenticator in all apps that is sharing the authentication session. For details read [this document](https://developer.android.com/reference/android/accounts/AbstractAccountAuthenticator).
147+
- The first installed app will be the "authenticator" app in the android, which in fact owns the accounts. Once the app was removed, the accounts will be removed together with the app.
148+
149+
## Security Considerations
150+
151+
### Binding Tokens To Device
152+
153+
device_secret should always be bound to a device.
154+
155+
[OAuth 2.0 Demonstrating Proof of Possession](https://datatracker.ietf.org/doc/html/rfc9449) is implemented for such binding.
156+
157+
The DPoP Proof MAY be provided when making requests to the /token endpoint. If DPoP Proof is provided, device_secret returned in the response will be bound to the public key provided in the DPoP Proof. Which the keypair used to sign such DPoP Proof is expected to be stored in a secure device storage such as iOS Keychain or Android Keystore.

docs/specs/oidc-sso-device.md

Lines changed: 0 additions & 93 deletions
This file was deleted.

0 commit comments

Comments
 (0)