Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix OAuth token retrieval issue #793

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,33 @@ ApiClient client = Clients.builder()
[//]: # (end: createClient)

Hard-coding the Okta domain and API token works for quick tests, but for real projects you should use a more secure way of storing these values (such as environment variables). This library supports a few different configuration sources, covered in the [configuration reference](#configuration-reference) section.

## OAuth 2.0

Okta allows you to interact with Okta APIs using scoped OAuth 2.0 access tokens. Each access token enables the bearer to perform specific actions on specific Okta endpoints, with that ability controlled by which scopes the access token contains.

This SDK supports this feature only for service-to-service applications. Check out [our guides](https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/overview/) to learn more about how to register a new service application using a private and public key pair.

Check out [our guide](https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/main/#generate-the-jwk-using-the-admin-console) to learn how to generate a JWK and convert the same to PEM format which would be used as PrivateKey in `Client` creation.

When using this approach, you will not need an API Token because the SDK will request an access token for you. In order to use OAuth 2.0, construct a client instance by passing the following parameters:

[//]: # (method: createOAuth2Client)
```java
ApiClient client = Clients.builder()
.setOrgUrl("https://{yourOktaDomain}") // e.g. https://dev-123456.okta.com
.setAuthorizationMode(AuthorizationMode.PRIVATE_KEY)
.setClientId("{clientId}")
.setKid("{kid}") // optional
.setScopes(new HashSet<>(Arrays.asList("okta.users.manage", "okta.apps.manage", "okta.groups.manage")))
.setPrivateKey("/path/to/yourPrivateKey.pem")
// (or) .setPrivateKey("full PEM payload")
// (or) .setPrivateKey(Paths.get("/path/to/yourPrivateKey.pem"))
// (or) .setPrivateKey(inputStream)
// (or) .setPrivateKey(privateKey)
.build();
```
[//]: # (end: createOAuth2Client)

## Usage guide

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.okta.sdk.authc.credentials.TokenClientCredentials;
import com.okta.sdk.cache.Caches;
import com.okta.sdk.client.AuthorizationMode;
import com.okta.sdk.client.Clients;
import com.okta.sdk.resource.common.PagedList;
import com.okta.sdk.resource.group.GroupBuilder;
Expand Down Expand Up @@ -56,6 +57,7 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;

Expand All @@ -81,6 +83,21 @@ private void createClient() {
.build();
}

private void createOAuth2Client() {
ApiClient client = Clients.builder()
.setOrgUrl("https://{yourOktaDomain}") // e.g. https://dev-123456.okta.com
.setAuthorizationMode(AuthorizationMode.PRIVATE_KEY)
.setClientId("{clientId}")
.setKid("{kid}") // optional
.setScopes(new HashSet<>(Arrays.asList("okta.users.manage", "okta.apps.manage", "okta.groups.manage")))
.setPrivateKey("/path/to/yourPrivateKey.pem")
// (or) .setPrivateKey("full PEM payload")
// (or) .setPrivateKey(Paths.get("/path/to/yourPrivateKey.pem"))
// (or) .setPrivateKey(inputStream)
// (or) .setPrivateKey(privateKey)
.build();
}

private void getUser() {
UserApi userApi = new UserApi(client);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ public ApiClient build() {
}

ApiClient apiClient = new ApiClient(restTemplate(this.clientConfig), this.cacheManager);
apiClient.setBasePath(this.clientConfig.getBaseUrl());

if (!isOAuth2Flow()) {
if (this.clientConfig.getClientCredentialsResolver() == null && this.clientCredentials != null) {
Expand All @@ -357,7 +358,6 @@ public ApiClient build() {
this.clientConfig.setClientCredentialsResolver(new DefaultClientCredentialsResolver(this.clientConfig));
}

apiClient.setBasePath(this.clientConfig.getBaseUrl());
apiClient.setApiKeyPrefix("SSWS");
apiClient.setApiKey((String) this.clientConfig.getClientCredentialsResolver().getClientCredentials().getCredentials());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@

/**
* Implementation of {@link AccessTokenRetrieverService} interface.
*
* This has logic to fetch OAuth2 access token from the Authorization server endpoint.
*
* @since 1.6.0
*/
public class AccessTokenRetrieverServiceImpl implements AccessTokenRetrieverService {
Expand Down Expand Up @@ -92,10 +90,6 @@ public OAuth2AccessToken getOAuth2AccessToken() throws IOException, InvalidKeyEx
String scope = String.join(" ", tokenClientConfiguration.getScopes());

try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("grant_type", "client_credentials");
queryParams.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
Expand All @@ -107,19 +101,21 @@ public OAuth2AccessToken getOAuth2AccessToken() throws IOException, InvalidKeyEx
Collections.emptyMap(),
queryParams,
null,
httpHeaders,
new HttpHeaders(),
new LinkedMultiValueMap<>(),
null,
Collections.singletonList(MediaType.APPLICATION_JSON),
MediaType.APPLICATION_JSON,
new String[] { "OAuth_2.0" },
MediaType.APPLICATION_FORM_URLENCODED,
new String[] { "oauth2" },
new ParameterizedTypeReference<OAuth2AccessToken>() {});

OAuth2AccessToken oAuth2AccessToken = responseEntity.getBody();

log.debug("Got OAuth2 access token for client id {} from {}",
tokenClientConfiguration.getClientId(), tokenClientConfiguration.getBaseUrl() + TOKEN_URI);

apiClient.setAccessToken(oAuth2AccessToken.getAccessToken());

return oAuth2AccessToken;
} catch (ResourceException e) {
Error defaultError = e.getError();
Expand Down Expand Up @@ -252,7 +248,8 @@ ClientConfiguration constructTokenClientConfig(ClientConfiguration apiClientConf
if (apiClientConfiguration.getProxy() != null)
tokenClientConfiguration.setProxy(apiClientConfiguration.getProxy());

tokenClientConfiguration.setAuthenticationScheme(AuthenticationScheme.NONE);
tokenClientConfiguration.setBaseUrl(apiClientConfiguration.getBaseUrl());
tokenClientConfiguration.setAuthenticationScheme(AuthenticationScheme.OAUTH2_PRIVATE_KEY);
tokenClientConfiguration.setAuthorizationMode(AuthorizationMode.get(tokenClientConfiguration.getAuthenticationScheme()));
tokenClientConfiguration.setClientId(apiClientConfiguration.getClientId());
tokenClientConfiguration.setScopes(apiClientConfiguration.getScopes());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.okta.sdk.impl.oauth2;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.time.Duration;
import java.time.Instant;

Expand All @@ -31,16 +33,16 @@ public class OAuth2AccessToken {
public static final String ACCESS_TOKEN_KEY = "access_token";
public static final String SCOPE_KEY = "scope";

/* Token error constants */
public static final String ERROR_KEY = "error";
public static final String ERROR_DESCRIPTION = "error_description";

@JsonProperty(TOKEN_TYPE_KEY)
private String tokenType;

@JsonProperty(EXPIRES_IN_KEY)
private Integer expiresIn;

@JsonProperty(ACCESS_TOKEN_KEY)
private String accessToken;

@JsonProperty(SCOPE_KEY)
private String scope;

private Instant issuedAt = Instant.now();
Expand Down