Native SSO
Introduction
This article reproduces selected sections from the document “Native SSO” on Authlete’s website, which explains the “OpenID Connect Native for Mobile Apps 1.0” specification and Authlete’s implementation. For additional information, such as sample implementation of Native SSO, please refer to the original document provided by Authlete.
Concept
OpenID Connect Native SSO for Mobile Apps 1.0 (hereafter referred to as “Native SSO”) is a standard specification that defines a mechanism for achieving single sign-on (SSO) across multiple mobile applications under the control of the same vendor. If this specification is implemented, users are not required to authenticate individually for each mobile application; instead, a single authentication suffices for all applications integrated via Native SSO.
Specification Overview
Before diving into the details, let’s start with an overview of the specification.
The first application (hereafter referred to as “App 1”) obtains the following set of tokens using the authorization code flow. Notably, the ID token conforms to the Native SSO specification, and a new type of token called a device secret is included.
- access token
- refresh token (optional)
- ID token (Native SSO-compliant)
- device secret
App 1 stores the ID token and the device secret in shared storage that the second application (hereafter referred to as “App 2”) can access.
Next, App 2 retrieves the ID token and device secret from the shared storage and sends a token exchange request using them as parameters.
In response, a token exchange response containing a new set of tokens for App 2 is returned.
To summarize, the following diagram illustrates the overview.
Device Secret
The device secret is described in the Native SSO specification as follows:
The device secret contains relevant data to the device and the current users authenticated with the device. The device secret is completely opaque to the client and as such the AS MUST adequately protect the value such as using a JWE if the AS is not maintaining state on the backend.
When issuing a device secret, the OpenID Provider obtains some form of information about the device and associates it with the device secret. Then, upon receiving a token exchange request, it verifies whether the device associated with the device secret in the request matches the device from which the request was sent.
Specification Details
App 1 Authorization Request
App 1 sends an authorization request based on the authorization code flow to the OpenID Provider via a web browser. A Native SSO-specific requirement is that the scope
parameter must include both the openid
scope and the device_sso
scope. The device_sso
scope is defined by the Native SSO specification.
The minimum required request parameters for an authorization request compliant with Native SSO are as follows:
client_id
— The client identifier, such asapp_1
.response_type
— A space-separated list of the tokens being requested. When requesting an authorization code, it includescode
.scope
— A space-separated list of the scopes being requested. To comply with the Native SSO specification, bothopenid
anddevice_sso
must be included.redirect_uri
— The redirect URI. According to the OpenID Connect specification, theredirect_uri
parameter is required when requesting theopenid
scope.
Here is an example of an authorization request:
https://trial.authlete.net/api/authorization?client_id=app_1&response_type=code&scope=openid+device_sso&redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection
This example actually works. When the authorization page is displayed, please enter inga
and inga
as the login ID and password, respectively. If you are already logged in and would like to display the login ID field and password field again, add &prompt=login
at the end of the authorization request.
App 1 Token Request
A token request is constructed using the authorization code obtained from the above authorization request. The request parameters required are as follows:
grant_type
— This is the grant type. It is a mandatory parameter regardless of the flow used. For the authorization code flow, the value should be specified asauthorization_code
.code
— This is a mandatory parameter in the authorization code flow. It specifies the authorization code obtained from the authorization request.redirect_uri
— This is the redirect URI. If theredirect_uri
parameter was included in the preceding authorization request, it is also required in the token request. Its value must be the same as the one specified in the authorization request.
In addition to the above, depending on the application’s client type (RFC 6749 Section 2.1), whether it is confidential or public, and which client authentication method is used in the case of a confidential client, additional parameters may be required.
For example, if the client type is public, the client_id
request parameter is mandatory. On the other hand, if the client type is confidential and the private_key_jwt
client authentication method is used, the client_assertion
and client_assertion_type
request parameters are required. For more details on client authentication methods, refer to “OAuth 2.0 Client Authentication”.
The following is an example of a token request by a public client:
POST https://trial.authlete.net/api/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_id=app_1&grant_type=authorization_code&code={{authorization_code}}&redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection
If you try this example in practice, replace {{authorization_code}}
with the actual value (the authorization code obtained as a result of the authorization request).
App 1 Token Response
The token response that conforms to Native SSO includes, in addition to the access token and refresh token (optional), an ID token and a device secret that comply with Native SSO. The device secret is returned as the value of the device_secret
property.
{
"access_token": "R28TIqhCydVvH2x2a3XsOzJykFEs7yFotO4ip-a2MbY",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "openid device_sso",
"refresh_token": "lmlNXafSApRDAq7gZvy40ojya9bplgFSHczms46mTms",
"id_token": "eyJraWQiOiJaWUdJT0hZdUE5SXBVaWpWd1FOdWwzbkU1MzZ4MUpTV0hpT2ZkUzdzYWRnIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsInN1YiI6IjEwMDQiLCJhdWQiOlsiYXBwXzEiXSwiZXhwIjoxNzQ2NDM3MTE5LCJpYXQiOjE3NDYzNTA3MTksImF1dGhfdGltZSI6MTc0NjM1MDY3MiwiZHNfaGFzaCI6IlhrYmdHQ1JKUTFOQUhuS25NbjhKMFhIS25fOEVNenhCOWFRdUZITk0ycDQiLCJzaWQiOiJub2RlMDM4Y2F0N2ozMDhzZzE4MjhtMXNnMmRleGwzIn0.JAYlCEbGhjJwpgSZ4lUNaXkWD2ICeDs6FCBd3bKRvKPhrrGZKUAZDRij_Bmn_AF7DyTQS5ALHl82cJqjaLCcIw",
"device_secret": "b81d5ae9-9f85-4c6d-8658-1a36ffa42c83"
}
The value of the id_token
property included in the token response is the ID token:
eyJraWQiOiJaWUdJT0hZdUE5SXBVaWpWd1FOdWwzbkU1MzZ4MUpTV0hpT2ZkUzdzYWRnIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsInN1YiI6IjEwMDQiLCJhdWQiOlsiYXBwXzEiXSwiZXhwIjoxNzQ2NDM3MTE5LCJpYXQiOjE3NDYzNTA3MTksImF1dGhfdGltZSI6MTc0NjM1MDY3MiwiZHNfaGFzaCI6IlhrYmdHQ1JKUTFOQUhuS25NbjhKMFhIS25fOEVNenhCOWFRdUZITk0ycDQiLCJzaWQiOiJub2RlMDM4Y2F0N2ozMDhzZzE4MjhtMXNnMmRleGwzIn0.JAYlCEbGhjJwpgSZ4lUNaXkWD2ICeDs6FCBd3bKRvKPhrrGZKUAZDRij_Bmn_AF7DyTQS5ALHl82cJqjaLCcIw
The payload of this ID token, when decoded using base64url, is as follows. The ID token compliant with Native SSO specification includes the ds_hash
claim and the sid
claim.
{
"iss": "https://trial.authlete.net",
"sub": "1004",
"aud": [
"app_1"
],
"exp": 1746437119,
"iat": 1746350719,
"auth_time": 1746350672,
"ds_hash": "XkbgGCRJQ1NAHnKnMn8J0XHKn_8EMzxB9aQuFHNM2p4",
"sid": "node038cat7j308sg1828m1sg2dexl3"
}
The ds_hash
claim is the hash value of the device secret. How this hash value is calculated depends on the implementation, but the ds_hash
claim allows the ID token to be associated with the device secret.
The sid
claim is a string that uniquely identifies the user’s authentication session, essentially the session ID.
App 1 stores the obtained ID token and device secret in a location accessible to other applications that are integrated with Native SSO.
App 2 Token Request
The Native SSO specification extends the RFC 8693: OAuth 2.0 Token Exchange specification by adding requirements to achieve Native SSO. App 2 retrieves the ID token and device secret stored by App 1 and uses them to construct a token exchange request that complies with the Native SSO specification.
The request parameters for a token exchange request compliant with the Native SSO specification are as follows:
grant_type
— The grant type. In a token exchange request, specifyurn:ietf:params:oauth:grant-type:token-exchange
.audience
— The intended recipient of the token issued by the token exchange request. In a Native SSO token exchange request, specify the identifier of the OpenID Provider.subject_token
— The token that indicates on whose behalf the token exchange is being performed. In a Native SSO token exchange request, this is the ID token.subject_token_type
— The identifier indicating the type of thesubject_token
. In a Native SSO token exchange request, thesubject_token
is always an ID token, so the value must beurn:ietf:params:oauth:token-type:id_token
.actor_token
— The token that represents the actor executing the token exchange. In a Native SSO token exchange request, this is the device secret.actor_token_type
— The identifier indicating the type of theactor_token
. In a Native SSO token exchange request, theactor_token
is always a device secret, so the value must beurn:openid:params:token-type:device-secret
. This is a token type newly defined by the Native SSO specification.scope
— The scopes to associate with the access token issued as a result of the token exchange request. This parameter is optional.
In addition to the above, depending on the application’s client type (RFC 6749 Section 2.1), whether it is confidential or public, and which client authentication method is used in the case of a confidential client, additional parameters may be required.
For example, if the client type is public, the client_id
request parameter is mandatory. On the other hand, if the client type is confidential and the private_key_jwt
client authentication method is used, the client_assertion
and client_assertion_type
request parameters are required. For more details on client authentication methods, refer to “OAuth 2.0 Client Authentication”.
The following is an example of a token exchange request by a public client:
POST https://trial.authlete.net/api/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_id=app_2&grant_type=urn:ietf:params:oauth:grant-type:token-exchange&audience=https://trial.authlete.net&subject_token={{id_token}}&subject_token_type=urn:ietf:params:oauth:token-type:id_token&actor_token={{device_secret}}&actor_token_type=urn:openid:params:token-type:device-secret&scope=openid
If you want to try this example in practice, replace {{id_token}}
and {{device_secret}}
with actual values.
App 2 Token Response
The response to the token exchange request is almost identical to the token response of the authorization code flow. The only difference is that it includes the issued_token_type
property. In the case of Native SSO, the value of issued_token_type
is urn:ietf:params:oauth:token-type:access_token
.
{
"access_token": "rH9115-g83z9zIiCJ1mzIe8mza3bX4NaBTWmGs5qqow",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "openid",
"refresh_token": "_F7NMCU1ny8DQ-3Pru_owgII52gIew0T6wuWKeIrfL4",
"id_token": "eyJraWQiOiJaWUdJT0hZdUE5SXBVaWpWd1FOdWwzbkU1MzZ4MUpTV0hpT2ZkUzdzYWRnIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsInN1YiI6IjEwMDQiLCJhdWQiOlsiYXBwXzIiXSwiZXhwIjoxNzQ2NDM4MzUxLCJpYXQiOjE3NDYzNTE5NTEsImRzX2hhc2giOiJYa2JnR0NSSlExTkFIbktuTW44SjBYSEtuXzhFTXp4QjlhUXVGSE5NMnA0Iiwic2lkIjoibm9kZTAzOGNhdDdqMzA4c2cxODI4bTFzZzJkZXhsMyJ9.8jNNF5mpeHnbqp1FTK_1adR8FlgPmHK9_rwUzaz-o5P7RMyaelBaSj74IhxHY6wbCJeD0n_N14h8vD8zWYh-8w",
"device_secret": "b81d5ae9-9f85-4c6d-8658-1a36ffa42c83",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}
Extracting the ID token from the above response example:
eyJraWQiOiJaWUdJT0hZdUE5SXBVaWpWd1FOdWwzbkU1MzZ4MUpTV0hpT2ZkUzdzYWRnIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsInN1YiI6IjEwMDQiLCJhdWQiOlsiYXBwXzIiXSwiZXhwIjoxNzQ2NDM4MzUxLCJpYXQiOjE3NDYzNTE5NTEsImRzX2hhc2giOiJYa2JnR0NSSlExTkFIbktuTW44SjBYSEtuXzhFTXp4QjlhUXVGSE5NMnA0Iiwic2lkIjoibm9kZTAzOGNhdDdqMzA4c2cxODI4bTFzZzJkZXhsMyJ9.8jNNF5mpeHnbqp1FTK_1adR8FlgPmHK9_rwUzaz-o5P7RMyaelBaSj74IhxHY6wbCJeD0n_N14h8vD8zWYh-8w
and decoding its payload part using base64url will show this result:
{
"iss": "https://trial.authlete.net",
"sub": "1004",
"aud": [
"app_2"
],
"exp": 1746438351,
"iat": 1746351951,
"ds_hash": "XkbgGCRJQ1NAHnKnMn8J0XHKn_8EMzxB9aQuFHNM2p4",
"sid": "node038cat7j308sg1828m1sg2dexl3"
}
The values of the ds_hash
and sid
claims are the same as those in the ID token received by App 1, but the value of the aud
claim is different. In this ID token, the identifier of App 2, app_2
, is included in the aud
array.
References
- Specification: OpenID Connect Native SSO for Mobile Apps 1.0
- Specification: RFC 8693: OAuth 2.0 Token Exchange
- Native SSO Specification Source Code: openid-connect-native-sso-1_0.xml
- Native SSO Specification Issue Tracker: bitbucket.org/openid/connect/issues
- Documentation: RFC 8693 OAuth 2.0 Token Exchange