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****
.
- Log on to the RAM console by using your Alibaba Cloud account.
- In the left-side navigation pane, choose .
- On the Role-based SSO tab, click the OIDC tab. Then, click Create IdP.
- 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 theaud
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. - 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.
- Log on to the RAM console by using your Alibaba Cloud account.
- In the left-side navigation pane, choose .
- On the Roles page, click Create Role.
- In the Create Role panel, select IdP for Select Trusted Entity and click Next.
- Specify the RAM Role Name and Note parameters.
- Select OIDC for IdP Type.
- Select a trusted IdP, specify the conditions in the Conditions section, and then click OK. The following table describes the supported conditions.
Condition key Description Required Example 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**** - 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.
- Log on to the RAM console by using your Alibaba Cloud account.
- In the left-side navigation pane, choose .
- On the Roles page, find the RAM role to which you want to grant permissions and click Add Permissions in the Actions column.
- In the Add Permissions panel, grant permissions to the RAM role.
- Click OK.
- 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.
- 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 URLredirect_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"; } }
- Sample code for a static page
- 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 toopenid
.response_type
: Set this parameter totoken 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 receiveaccess_token
orid_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 ofid_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
- Parse the OIDC token.
You can parse the results that you obtained in Substep 2 and query the details about
header
andpayload
.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.