All Products
Search
Document Center

Resource Access Management:Implement OIDC-based SSO from Okta

Last Updated:Jul 09, 2024

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 as a RAM administrator.

  2. In the left-side navigation pane, choose Integrations > SSO.

  3. On the Role-based SSO tab, click the OIDC tab. Then, click Add 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.

    Issuer URL

    The URL of the issuer that is provided by an external IdP. The URL of the issuer must start with https and be a valid URL. The URL cannot contain query parameters that follow a question mark (?), logon information that is identified by at signs (@), or fragment that is identified by 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.

    After you specify a valid value for Issuer URL, you can click Obtain Fingerprint. 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.

    Note

    If you want to rotate the certificate of your IdP, we recommend that you generate the fingerprint of the new certificate and add the fingerprint to the OIDC IdP that you created in the RAM console before the rotation. After at least one day, rotate the certificate. You can delete the previous fingerprint after you obtain a Security Token Service (STS) token.

    Client IDs

    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 a 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 specified 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.

    Earliest Issuance Time Allowed

    The time limit on an OIDC token. If an OIDC token is issued earlier than the time limit, the OIDC token cannot be used to obtain an STS token.

    Default value: 12 hours. Valid values: 1 to 168 hours.

    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 as a RAM user who has administrative rights.

  2. In the left-side navigation pane, choose Identities > Roles.

  3. On the Roles page, click Create Role.

  4. On the Create Role page, select IdP in the Select Trusted Entity section and click Next.

  5. Specify the RAM Role Name and Note parameters.

  6. Select OIDC for the IdP Type parameter.

  7. Select a trusted IdP and specify the conditions.

    The following table describes the supported conditions.

    Condition key

    Description

    Required

    Examples

    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

    https://dev-xxxxxx.okta.com

    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

    0oa294vi1vJoClev****

    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

    00u294e3mzNXt4Hi****

  8. Click OK.

  9. 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 as a RAM administrator.

  2. In the left-side navigation pane, choose Identities > Roles.

  3. On the Roles page, find the RAM role that you want to manage and click Grant Permission in the Actions column.

    image

    You can also select multiple RAM roles and click Grant Permission in the lower part of the RAM role list to grant permissions to multiple RAM roles at a time.

  4. In the Grant Permission panel, grant permissions to the RAM role.

    1. Configure the Resource Scope parameter.

      • Account: The authorization takes effect on the current Alibaba Cloud account.

      • Resource Group: The authorization takes effect on a specific resource group.

        Note

        If you select Resource Group for the Resource Scope parameter, make sure that the required cloud service supports resource groups. For more information, see Services that work with Resource Group.

    2. Configure the Principal parameter.

      The principal is the RAM role to which you want to grant permissions. The current RAM role is automatically selected.

    3. Configure the Policy parameter.

      A policy is a set of access permissions. You can select multiple policies at a time.

      • System policies: policies that are created by Alibaba Cloud. You can use but cannot modify these policies. Version updates of the policies are maintained by Alibaba Cloud. For more information, see Services that work with RAM.

        Note

        The system automatically identifies high-risk system policies, such as AdministratorAccess and AliyunRAMFullAccess. We recommend that you do not grant unnecessary permissions by attaching high-risk policies.

      • Custom policies: You can manage and update custom policies based on your business requirements. You can create, update, and delete custom policies. For more information, see Create a custom policy.

    4. Click Grant permissions.

  5. Click Close.

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.