Skip to content
Connect2id

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;
}