Native SSO
Native SSO is a new OpenID Connect extension designed to streamline the user experience mobile or desktop app suites by utilising a device session. The Connect2id server rolled out support for native SSO in v16.0, based on server-side device sessions and other enhancements.
The following example using the SDK demonstrates a standard native SSO flow.
The first client app performs a regular code flow to authenticate the end-user,
using the special device_sso
scope value to signal its intent to obtain a
device_secret
for the purposes of native SSO.
The second client app uses the native SSO to sign-in the end-user and obtain tokens by making a direct back-channel token request, saving the end-user from the redirection to the OpenID provider to login into the app.
import com.nimbusds.jwt.*;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.oauth2.sdk.tokenexchange.*;
import com.nimbusds.openid.connect.sdk.*;
import com.nimbusds.openid.connect.sdk.nativesso.*;
import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
import java.net.URI;
// The first native app signs in the user using the regular code flow
ClientID nativeClient_1 = new ClientID("zae1Do9f");
URI nativeClient_1_redirectURI = URI.create("https://app.example.com/cb");
State state = new State();
AuthenticationRequest authRequest = new AuthenticationRequest.Builder(
ResponseType.CODE,
new Scope("openid", "device_sso"),
nativeClient_1,
nativeClient_1_redirectURI)
.state(state)
.endpointURI(URI.create("https://c2id.com/login"))
.build();
URI loginURI = authRequest.toURI();
URI callback = /* the app receives the callback at the redirect_uri */;
AuthenticationResponse authResponse = AuthenticationResponseParser.parse(callback);
if (! authResponse.indicatesSuccess()) {
System.err.println("Login failed: " + authResponse.toErrorResponse().getErrorObject());
return;
}
AuthenticationSuccessResponse authSuccess = authResponse.toSuccessResponse();
if (! state.equals(authSuccess.getState())) {
System.err.println("State mismatch");
return;
}
TokenRequest tokenRequest = new TokenRequest.Builder(
URI.create("https://c2id.com/token"),
nativeClient_1,
new AuthorizationCodeGrant(authSuccess.getAuthorizationCode(), nativeClient_1_redirectURI))
.build();
HTTPResponse httpResponse = tokenRequest.toHTTPRequest().send();
OIDCTokenResponse tokenResponse = OIDCTokenResponse.parse(httpResponse);
if (! tokenResponse.indicatesSuccess()) {
System.err.println(tokenResponse.toErrorResponse().getErrorObject());
return;
}
OIDCTokens oidcTokens = tokenResponse.toSuccessResponse().getOIDCTokens();
// The app must store the ID token and the device secret in a secure location
// that is accessible to the other trusted apps that are allowed to share the
// device session with it
JWT idToken = oidcTokens.getIDToken();
DeviceSecret deviceSecret = oidcTokens.getDeviceSecret();
if (deviceSecret == null) {
System.out.println("Native SSO not supported");
return;
}
// The second native app makes a direct token request benefiting from the SSO
ClientID nativeClient_2 = new ClientID("Thai8cha");
tokenRequest = new TokenRequest.Builder(
URI.create("https://c2id.com/token"),
nativeClient_2,
new TokenExchangeGrant(
new TypelessToken(idToken.serialize()),
TokenTypeURI.ID_TOKEN,
new TypelessToken(deviceSecret.getValue()),
TokenTypeURI.DEVICE_SECRET,
null,
new Audience("https://c2id.com").toSingleAudienceList()))
.scope(new Scope("openid"))
.build();
httpResponse = tokenRequest.toHTTPRequest().send();
tokenResponse = OIDCTokenResponse.parse(httpResponse);
if (! tokenResponse.indicatesSuccess()) {
System.err.println(tokenResponse.toErrorResponse().getErrorObject());
return;
}
oidcTokens = tokenResponse.toSuccessResponse().getOIDCTokens();
idToken = oidcTokens.getIDToken();
deviceSecret = oidcTokens.getDeviceSecret();
if (deviceSecret == null) {
System.out.println("Native SSO error");
return;
}