This topic provides an example on how to implement OpenID Connect (OIDC)-based single sign-on (SSO) from Okta to Alibaba Cloud. Then, applications that are registered in Okta can access Alibaba Cloud resources by using Security Token Service (STS) tokens in a secure manner.

Prerequisites

An OIDC application is registered in Okta. The URL of the issuer and the client ID of the application are obtained. The following data is used in this example:
  • The URL of the issuer is https://dev-xxxxxx.okta.com.
  • The client ID is 0oa294vi1vJoClev****.

Step 1: Create an OIDC identity provider (IdP) in Alibaba Cloud

In this step, an OIDC IdP named TestOidcProvider is created. The URL of the issuer is https://dev-xxxxxx.okta.com and the client ID is 0oa294vi1vJoClev****.

  1. Log on to the RAM console by using your Alibaba Cloud account.
  2. In the left-side navigation pane, click SSO.
  3. On the Role-based SSO tab, click the OIDC tab. Then, click Create IdP.
  4. On the Create IdP page, configure the following parameters.
    Parameter Description
    IdP Name The name must be unique within an Alibaba Cloud account.
    IdP URL The URL of the issuer that is provided by an external IdP. The URL of the issuer must start with https and be in the valid URL format. The URL cannot contain query parameters that follow a question mark (?) or logon information that is identified by at signs (@). The URL cannot be a fragment URL that contains number signs (#).
    Fingerprint The fingerprint that is generated based on the HTTPS certificate of an external IdP. You can use a fingerprint to prevent the URL of the issuer from being hijacked or tampered with. Alibaba Cloud calculates the fingerprint. We recommend that you calculate the fingerprint on your computer. For example, you can use OpenSSL to calculate the fingerprint. Then, you can compare the calculation result with the calculation result provided by Alibaba Cloud. For more information about OpenSSL, visit the official website of OpenSSL. If the calculation results are different, the URL of the issuer may have been attacked. Make sure that you enter a valid fingerprint.
    Client ID The ID that is generated for an application when you register the application in the external IdP. When you apply for an OIDC token from an external IdP, you must use the client ID. The client ID is specified in the aud field of the OIDC token that is issued. When you create an OIDC IdP, you must configure the client ID. If you want to use the OIDC token to obtain an STS token, Alibaba Cloud checks whether the client ID that is included in the aud field is the same as the client ID that you configured in the OIDC IdP. You can assume a RAM role only when the client IDs are the same.

    If multiple clients need to access Alibaba Cloud resources, you can configure multiple client IDs. You can configure a maximum of 20 client IDs.

    Remarks The description of the OIDC IdP.
  5. Click OK.

Step 2: Create a RAM role for the OIDC IdP in Alibaba Cloud

In this step, a RAM role named testoidc is created and the TestOidcProvider OIDC IdP that you created in Step 1 is selected.

  1. Log on to the RAM console by using your Alibaba Cloud account.
  2. In the left-side navigation pane, choose Identities > Roles.
  3. On the Roles page, click Create Role.
  4. In the Create Role panel, select IdP for Select Trusted Entity and click Next.
  5. Specify RAM Role Name and Note.
  6. Select OIDC for the IdP Type parameter.
  7. Select a trusted IdP, read the conditions, and then click OK.
    The following table describes the supported conditions.
    Condition key Description Required
    oidc:iss The issuer. You can assume the RAM role only if the iss field of the OIDC token that you want to use to assume the RAM role meets this condition.

    The conditional operator must be StringEquals. The value must be the URL of the issuer that you specify for the selected OIDC IdP. You can specify this condition to ensure that you can use the OIDC token to assume the RAM role only if the OIDC token is issued by a trusted IdP.

    Yes
    oidc:aud The audience. You can assume the RAM role only if the aud field of the OIDC token that you want to use to assume the RAM role meets this condition.

    The conditional operator must be StringEquals. The value can be one or more client IDs that you specify for the selected OIDC IdP. You can specify this condition to ensure that you can use the OIDC token to assume the RAM role only if the OIDC token is generated by using the client ID that you specify.

    Yes
    oidc:sub The subject. You can assume the RAM role only if the sub field of the OIDC token that you want to use to assume the RAM role meets this condition.

    The conditional operator can be a string of all types. The value can be up to 10 subjects. You can specify this condition to further limit the identity that you can use to assume the RAM role. You can also leave this condition unspecified.

    No
  8. Click Close.

Step 3: Grant permissions to the RAM role

You can grant permissions to the RAM role named testoidc that you created in Step 2 to access Alibaba Cloud resources based on your business requirements.

  1. Log on to the RAM console by using your Alibaba Cloud account.
  2. In the left-side navigation pane, choose Identities > Roles.
  3. On the Roles page, find the RAM role to which you want to grant permissions and click Add Permissions in the Actions column.
  4. In the Add Permissions panel, grant permissions to the RAM role.
    1. Select the authorization scope.
      • Alibaba Cloud Account: The authorization takes effect on the current Alibaba Cloud account.
      • Specific Resource Group: The authorization takes effect on a specific resource group.
        Note If you select Specific Resource Group for Authorized Scope, make sure that the required cloud service supports resource groups. For more information, see Alibaba Cloud services that support resource groups.
    2. Specify the principal.
      The principal is the RAM role to which permissions are granted. By default, the current RAM role is specified. You can also specify a different RAM role.
    3. Select policies.
      Note You can attach a maximum of five policies to a RAM role at a time. If you need to attach more than five policies to a RAM role, perform the operation multiple times.
  5. Click OK.
  6. Click Complete.

Step 4: Issue an OIDC token in Okta

You cannot log on to the Alibaba Cloud Management Console by using OIDC. Therefore, you must implement OIDC-based SSO by using programmatic access. To obtain an OIDC token, you must complete authorization by using Open Authorization (OAuth). Therefore, you must use OAuth 2.0 to obtain an OIDC token from an OIDC IdP such as Okta. OAuth supports a variety of flows, such as the authorization code flow. For more information, see Authorization Code Flow. However, the authorization code flow is complex. In the following sections, the implicit flow is used to describe how to obtain an OIDC token and implement SSO. Some operations in the implicit flow are not described in this topic. For more information about the implicit flow, see Implicit Flow.

  1. Build a web application to receive an OIDC token that is issued by Okta.
    In this example, a simple web application that is built by using Java Spring Boot and Thymeleaf is used. The web application is deployed on your computer and is accessible over port 8080, and the localhost is resolved to 127.0.0.1. Therefore, you can enter localhost:8080 in a browser on your computer to access the web application. The following sample code is provided:
    • Sample code for a static page

      OAuth 2.0 requires that the callback information that Okta sends to the web application is passed in the fragment component of the callback URL. You can create a web page and obtain the OIDC token from the fragment component. In this example, a simple static page is created. Then, you can transparently pass the fragment component. The complete URL of this page is http://localhost:8080/accessTokenCallback, which is also the callback URL redirect_uri configured for the application in Okta.

      <!DOCTYPE HTML>
      <html xmlns:th="http://www.thymeleaf.org">
          <head>
              <script>
                  window.onload = function () {
                      let fragment = window.location.hash.substring(1);
                      window.location.href = "/receiveAccessToken?" + fragment;
                  };
              </script>
          </head>
      </html>
    • Sample code for a class

      A class is created as the controller of the static page.

      package com.aliyun.oauthtest;
      
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.RequestMapping;
      
      @Controller
      public class CallbackController {
          @RequestMapping("accessTokenCallback")
          public String callback() {
              return "accessTokenCallback";
          }
      }
  2. Log on to Okta and apply for an OIDC token from Okta.
    You must log on to Okta. Then, you can construct and access the URL https://dev-xxxxxx.okta.com/oauth2/v1/authorize?client_id=0oa294vi1vJoClev****&scope=openid&response_type=token%20id_token&state=testState&nonce=a_unique_nonce_1&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2FaccessTokenCallback by using the web application that is built in Step 1.

    The following list describes the parameters in the URL:

    • client_id: Set this parameter to the client ID of the OIDC application that is registered in Okta.
    • scope: Set this parameter to openid.
    • response_type: Set this parameter to token id_token in the implicit flow.
    • state: specifies the current status of the OIDC application. You can configure this parameter based on your business requirements.
    • nonce: This parameter is used to prevent replay attacks. You can configure this parameter based on your business requirements.
    • redirect_uri: Set this parameter to the callback URL that is used to receive access_token or id_token. In this example, set this parameter to the URL of the web application that you created in Substep 1.

    In this example, you have logged on to Okta. Therefore, the system redirects you to the callback URL based on the specified redirect_uri. The value of id_token in the following URL is the OIDC token.

    HTTP/1.1 302 Found
    Location:  http://localhost:8080/accessTokenCallback#id_token=eyJraWQiOiJ6OUV0e****&access_token=eyJraWQiOiJseEQ3R****&token_type=Bearer&expires_in=3600&scope=openid&state=testState
  3. Parse the OIDC token.

    You can parse the results that you obtained in Substep 2 and query the details about header and payload.

    Sample request:

    package com.aliyun.oauthtest;
    
    import java.util.Base64;
    import java.util.Base64.Decoder;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.TreeMap;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class ClientAppController {
    
    
        @RequestMapping(value = "/receiveAccessToken", method = {RequestMethod.POST, RequestMethod.GET},
                produces = "application/json")
        public Map<String, Object> receiveAccessToken(@RequestParam("access_token") String accessToken,
                                                      @RequestParam("id_token") String idToken,
                                                      @RequestParam("token_type") String tokenType,
                                                      @RequestParam("expires_in") Long expireTime,
                                                      @RequestParam("scope") String scope,
                                                      @RequestParam("state") String state) 
        {
            Map<String, Object> result = new TreeMap<>();
            result.put("access_token", accessToken);
            result.put("id_token", idToken);
            result.put("token_type", tokenType);
            result.put("expires_in", "" + expireTime);
            result.put("scope", scope);
            result.put("state", state);
    
            String[] jwt = idToken.split("\\.");
            Decoder decoder = Base64.getDecoder();
            result.put(" id token jwt header", JSON.parse(new String(decoder.decode(jwt[0]))));
            result.put(" id token jwt payload", JSON.parse(new String(decoder.decode(jwt[1]))));
            result.put(" id token jwt signature", jwt[2]);
            return result;
    
        }
    }

    Sample response:

    {
        " id token jwt header": {
            "kid": "z9EtyT345d-JLIJo2-5ySDO27LG4FPeOotbwJPT****",
            "alg": "RS256"
        },
        " id token jwt payload": {
            "at_hash": "KKsdN3prZWTvBEMn-g****",
            "sub": "00u294e3mzNXt4Hi****",
            "aud": "0oa294vi1vJoClev****",
            "ver": 1,
            "idp": "0oa294iehxjUCZIO****",
            "amr": [
                "pwd"
            ],
            "auth_time": 1636373097,
            "iss": "https://dev-xxxxxx.okta.com",
            "exp": 1636377759,
            "iat": 1636374159,
            "nonce": "a_unique_nonce_1",
            "jti": "ID.lmSU5AD2iKLCVu6_KLMIr52dpCprncxW38v-NCA****"
        },
        "id token jwt signature": "ZEJEGIv4Zoau63****",
        "access_token": "eyJraWQiOiJseEQ3R****",
        "expires_in": "3600",
        "id_token": "eyJraWQiOiJ6OUV0e****",
        "scope": "openid",
        "state": "testState",
        "token_type": "Bearer"
    }

Step 5: Use the OIDC token to obtain an STS token

To obtain an STS token, call the AssumeRoleWithOIDC operation. In the request, specify the unparsed OIDC token that you obtained in Step 4.

Sample request:

public static void main(String[] args)
{
    IAcsClient client = initialization();
    String jwtToken = "eyJraWQiOiJ6OUV0e****"; //The unparsed OIDC token that you obtained from Okta. The token is the value of id_token. 
    AssumeRoleWithOIDCRequest request = new AssumeRoleWithOIDCRequest();
    request.setDurationSeconds(3600L);
    request.setOIDCProviderArn("acs:ram::113511544585****:oidc-provider/TestOidcProvider");
    request.setOIDCToken(jwtToken);
    request.setRoleArn("acs:ram::113511544585****:role/testoidc");
    request.setRoleSessionName("TestOidcAssumedRoleSession");
    try
    {
        AssumeRoleWithOIDCResponse resp = client.getAcsResponse(request);
        System.out.println("success requestId: " + resp.getRequestId());
        System.out.println("success assume role arn: " + resp.getAssumedRoleUser().getArn());
        System.out.println("success sts credential accessKey id: " + resp.getCredentials().getAccessKeyId());
        System.out.println("success sts credential accessKey secret: " + resp.getCredentials().getAccessKeySecret());
        System.out.println("success resp: " + JSON.toJSONString(resp));
    }
    catch(ClientException | SystemException e)
    {
        e.printStackTrace();
    }
}

Sample response:

success requestId: 3D57EAD2-8723-1F26-B69C-F8707D8B565D
success assume role arn: acs:ram::113511544585****:role/testoidc/TestOidcAssumedRoleSession
success sts credential accessKey id: STS.NUgYrLnoC37mZZCNnAbez****
success sts credential accessKey secret: CVwjCkNzTMupZ8NbTCxCBRq3K16jtcWFTJAyBEv2****
success resp:
{
    "AssumedRoleUser":
    {
        "Arn": "acs:ram::113511544585****:role/testoidc/TestOidcAssumedRoleSession",
        "AssumedRoleId": "33157794895460****:TestOidcAssumedRoleSession"
    },
    "Credentials":
    {
        "AccessKeyId": "STS.NUgYrLnoC37mZZCNnAbez****",
        "AccessKeySecret": "CVwjCkNzTMupZ8NbTCxCBRq3K16jtcWFTJAyBEv2****",
        "Expiration": "2021-10-20T04:27:09Z",
        "SecurityToken": "CAIShwJ1q6Ft5B2yfSjIr****"
    },
    "OIDCTokenInfo":
    {
        "ClientIds": "0oa294vi1vJoClev****",
        "Issuer": "https://dev-xxxxxx.okta.com",
        "Subject": "00u294e3mzNXt4Hi****"
    },
    "RequestId": "3D57EAD2-8723-1F26-B69C-F8707D8B565D"
}

The information in Credentials is the information about the STS token.

Step 6: Use the STS token to access Alibaba Cloud resources

Use the STS token that you obtained from Step 5 to access the Alibaba Cloud resources on which you have permissions.