• Stars
    star
    175
  • Rank 218,059 (Top 5 %)
  • Language
    Go
  • License
    Other
  • Created over 6 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Golang SDK for interacting with the Okta management API, enabling server-side code to manage Okta users, groups, applications, and more.

Build Status License Support API Reference

Okta Golang management SDK

This repository contains the Okta management SDK for Golang. This SDK can be used in your server-side code to interact with the Okta management API and

We also publish these libraries for Golang:

You can learn more on the Okta + Golang page in our documentation.

Release status

This library uses semantic versioning and follows Okta's library version policy.

Version Status
0.x ⚠️ Beta Release (Retired)
1.x ⚠️ Retiring on 2021-03-04
2.x ✔️ Release

The latest release can always be found on the releases page.

Need help?

If you run into problems using the SDK, you can

Getting started

The SDK is compatible with Go version 1.12.x and up. For SDK v2 and above, you must use Go Modules to install the SDK.

Install current release

To install the Okta Golang SDK in your project:

  • Create a module file by running go mod init
    • You can skip this step if you already use go mod
  • Run go get github.com/okta/okta-sdk-golang/v2@latest. This will add the SDK to your go.mod file.
  • Import the package in your project with import "github.com/okta/okta-sdk-golang/v2/okta"

Installing legacy version

Although we do not suggest using the 1.x version of the SDK, you can still use it. Version 1.x is retiring and will not be supported past March 4, 2021. It will likely remain working after that date, but you should make a plan to migrate to the new 2.x version.

You can install v1 of the SDK by running go get github.com/okta/okta-sdk-golang@latest and import the package in your project with import "github.com/okta/okta-sdk-golang"

You'll also need

Initialize a client

Construct a client instance by passing it your Okta domain name and API token:

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)
}

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 section.

Usage guide

These examples will help you understand how to use this library. You can also browse the full API reference documentation.

Once you initialize a client, you can call methods to make requests to the Okta API. Most methods are grouped by the API endpoint they belong to. For example, methods that call the Users API are organized under client.User.

Caching

In the default configuration the client utilizes a memory cache that has a time to live on its cached values. See Configuration Setter Object WithCache(cache bool), WithCacheTtl(i int32), and WithCacheTti(i int32). This helps to keep HTTP requests to the Okta API at a minimum. In the case where the client needs to be certain it is accessing recent data; for instance, list items, delete an item, then list items again; be sure to make use of the refresh next facility to clear the request cache. See Refreshing Cache for Specific Call. To completely disable the request memory cache configure the client with WithCache(false).

NOTE: Regardless of cache manager, Access Tokens from OAuth requests are always cached.

Connection Retry / Rate Limiting

By default this SDK retries requests that are returned with a 429 exception. To disable this functionality set OKTA_CLIENT_REQUEST_TIMEOUT and OKTA_CLIENT_RATELIMIT_MAXRETRIES to 0.

Setting only one of the values to zero disables that check. Meaning, by default, four retry attempts will be made. If you set OKTA_CLIENT_REQUEST_TIMEOUT to 45 seconds and OKTA_CLIENT_RATELIMIT_MAXRETRIES to 0. This SDK will continue to retry indefinitely for 45 seconds. If both values are non zero, this SDK attempts to retry until either of the conditions are met (not both).

We use the Date header from the server to calculate the delta, as it's more reliable than system time. But always add 1 second to account for some clock skew in our service:

backoff_seconds = header['X-Rate-Limit-Reset'] - header['Date'] + 1s

If the backoff_seconds calculation exceeds the request timeout, the initial 429 response will be allowed through without additional attempts.

When creating your client, you can pass in these settings like you would with any other configuration.

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context,
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
    okta.WithRequestTimeout(45),
    okta.WithRateLimitMaxRetries(3),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)
}

Authenticate a User

This library should only be used with the Okta management API. To call the Authentication API, you should construct your own HTTP requests.

Get a User

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  user, resp, err := client.User.GetUser(ctx, "{UserId|Username|Email}")
  if err != nil {
    fmt.Printf("Error Getting User: %v\n", err)
  }
  fmt.Printf("User: %+v\n Response: %+v\n\n",user, resp)
}

List all Users

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  users, resp, err := client.User.ListUsers(ctx, nil)
  if err != nil {
    fmt.Printf("Error Getting Users: %v\n", err)
  }
  fmt.Printf("Users: %+v\n Response: %+v\n\n",users, resp)
  for index, user := range users {
    fmt.Printf("User %d: %+v\n", index, user)
  }
}

Filter or search for Users

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
	"github.com/okta/okta-sdk-golang/v2/okta/query"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  filter := query.NewQueryParams(query.WithFilter("status eq \"ACTIVE\""))

	filteredUsers, resp, err := client.User.ListUsers(ctx, filter)
	if err != nil {
		fmt.Printf("Error Getting Users: %v\n", err)
	}

	fmt.Printf("Filtered Users: %+v\n Response: %+v\n\n", filteredUsers, resp)

	for index, user := range filteredUsers {
		fmt.Printf("User %d: %+v\n", index, user)
	}
}

Create a User

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
	"github.com/okta/okta-sdk-golang/v2/okta/query"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  password := &okta.PasswordCredential{
		Value: "Abcd1234!",
	}

	userCredentials := &okta.UserCredentials{
		Password: password,
	}

	profile := okta.UserProfile{}
	profile["firstName"] = "Ben"
	profile["lastName"] = "Solo"
	profile["email"] = "[email protected]"
	profile["login"] = "[email protected]"

	createUserRequest := okta.CreateUserRequest{
		Credentials: userCredentials,
		Profile: &profile,
	}


	// activate user on create
	queryParam := query.NewQueryParams(query.WithActivate(true))


	user, resp, err := client.User.CreateUser(ctx, createUserRequest, queryParam)
	if err != nil {
		fmt.Printf("Error Creating User: %v\n", err)
	}

	fmt.Printf("User: %+v\n Response: %+v\n\n", user, resp)
}

Update a User

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
	"github.com/okta/okta-sdk-golang/v2/okta/query"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  	userToUpdate, resp, err := client.User.GetUser(ctx, "{userId}")
	if err != nil {
		fmt.Printf("Error Getting User to update: %v\n", err)
	}

	fmt.Printf("User to update: %+v\n Response: %+v\n\n", userToUpdate, resp)

	newProfile := *userToUpdate.Profile
	newProfile["nickName"] = "Kylo Ren"
	updateUser := &okta.User{
		Profile: &newProfile,
	}

	updatedUser, resp, err := client.User.UpdateUser(ctx, userToUpdate.Id, *updateUser, nil)
	if err != nil {
		fmt.Printf("Error updating user: %v\n", err)
	}

	fmt.Printf("Updated User: %+v\n Response: %+v\n\n", updatedUser, resp)
}

Get and set custom attributes

Custom attributes must first be defined in the Okta profile editor. Then, you can work with custom attributes on a user the same as any other profile attribute

Remove a User

You must first deactivate the user, and then you can delete the user.

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  resp, err := client.User.DeactivateUser(ctx, "00u14ffhw5szVqide0h8", nil)
	if err != nil {
		fmt.Printf("Error deactivating user: %v\n", err)
	}

	fmt.Printf("Response: %+v\n\n", resp)

	resp, err = client.User.DeactivateOrDeleteUser(ctx, "00u14ffhw5szVqide0h8", nil)
	if err != nil {
		fmt.Printf("Error deleting user: %v\n", err)
	}

	fmt.Printf("Response: %+v\n\n", resp)
}

List a User's Groups

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  groups, resp, err := client.User.ListUserGroups(ctx, "00u14fg9ff4MExj5f0h8")
	if err != nil {
		fmt.Printf("Error getting group list for user: %v\n", err)
	}

	fmt.Printf("Groups: %+v\n Response: %+v\n\n", groups, resp)

	for index, group := range groups {
		fmt.Printf("Group %d: %v\n", index, group)
	}
}

Create a Group

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  groupProfile := &okta.GroupProfile{
		Name: "Sith",
	}
	groupToCreate := &okta.Group{
		Profile: groupProfile,
	}
	group, resp, err := client.Group.CreateGroup(ctx, *groupToCreate)
	if err != nil {
		fmt.Printf("Error creating group: %v\n", err)
	}

	fmt.Printf("Created Group: %+v\n Response: %+v\n\n", group, resp)
}

Add a User to a Group

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  resp, err := client.Group.AddUserToGroup(ctx, "{groupId}", "{userId}")
	if err != nil {
		fmt.Printf("Error adding user to group: %v\n", err)
	}

	fmt.Printf("Response: %+v\n\n", resp)
}

List all Applications

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  applicationList, resp, err := client.Application.ListApplications(ctx, nil)
	if err != nil {
		fmt.Printf("Error listing applications: %v\n", err)
	}

	fmt.Printf("ApplicationList: %+v\n Response: %+v\n\n", applicationList, resp)

	// Listing applications is mapped and returned as a interface. Once you
	// get the list of applications, find the one you want to work on, and
	// make a `GET` request with that ID and the concrete application type
	for _, app := range applicationList {
		if app.(*okta.Application).Name == "oidc_client" {
			application, resp, err := client.Application.GetApplication(ctx, app.(*okta.Application).Id, okta.NewOpenIdConnectApplication(), nil)
			if err != nil {
				fmt.Printf("Error getting application: %v\n", err)
			}

			fmt.Printf("Concrete Application: %+v\n Response: %+v\n\n", application, resp)
		}
	}
}

Get an Application

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  application, resp, err := client.Application.GetApplication(ctx, "0oaswjmkbtlpBDWpu0h7", okta.NewOpenIdConnectApplication(), nil)
	if err != nil {
		fmt.Printf("Error getting application: %v\n", err)
	}

	fmt.Printf("Application: %+v\n Response: %+v\n\n", application, resp)

}

Create an Open ID Connect Application

import (
	"fmt"
	"context"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  swaApplicationSettingsApplication := okta.SwaApplicationSettingsApplication{
		ButtonField: "btn-login",
		PasswordField: "txtbox-password",
		UsernameField: "txtbox-username",
		Url: "http://example.com/login.html",
	}
	swaApplicationSettings := okta.SwaApplicationSettings{
		App: &swaApplicationSettingsApplication,
	}

	swaApp := okta.NewSwaApplication()
	swaApp.Label = "Empire Internals"
	swaApp.Settings = &swaApplicationSettings

	application, resp, err := client.Application.CreateApplication(ctx, swaApp, nil)
	if err != nil {
		fmt.Printf("Error creating application: %v\n", err)
	}

	fmt.Printf("Application: %+v\n Response: %+v\n\n", application, resp)
}

Call other API endpoints

Not every API endpoint is represented by a method in this library. You can call any Okta management API endpoint using this generic syntax:

import (
	"fmt"
	"context"
  "time"
	"github.com/okta/okta-sdk-golang/v2/okta"
)

func main() {
  ctx, client, err := okta.NewClient(
    context.TODO(),
    okta.WithOrgUrl("https://{yourOktaDomain}"),
    okta.WithToken("{apiToken}"),
  )

  if err != nil {
    fmt.Printf("Error: %v\n", err)
  }

  fmt.Printf("Context: %+v\n Client: %+v\n",ctx, client)

  // The URL you want to call (org url will be automatically prefixed)
  url := "/api/v1/authorizationServers"

  // Set up the body of the request with structs and data
	type Signing struct {
		RotationMode string     `json:"rotationMode,omitempty"`
		LastRotated  *time.Time `json:"lastRotated,omitempty"`
		NextRotation *time.Time `json:"nextRotation,omitempty"`
		Kid          string     `json:"kid,omitempty"`
	}

	type Credentials struct {
		Signing *Signing `json:"signing,omitempty"`
	}

	type AuthorizationServer struct {
		Id          string       `json:"id,omitempty"`
		Name        string       `json:"name,omitempty"`
		Description string       `json:"description,omitempty"`
		Audiences   []string     `json:"audiences,omitempty"`
		Issuer      string       `json:"issuer,omitempty"`
		IssuerMode  string       `json:"issuerMode,omitempty"`
		Status      string       `json:"status,omitempty"`
		Created     *time.Time   `json:"created,omitempty"`
		LastUpdated *time.Time   `json:"lastUpdated,omitempty"`
		Credentials *Credentials `json:"credentials,omitempty"`
		Embedded    interface{}  `json:"_embedded,omitempty"`
		Links       interface{}  `json:"_links,omitempty"`
	}

	as := AuthorizationServer{
	Name:        "Sample Authorization Server",
	Description: "Sample Authorization Server description",
	Audiences:   []string{"api://default"},
	}

  // create a new request using the cloned request executor
	req, err := client.CloneRequestExecutor().NewRequest("POST", url, as)
	if err != nil {
		fmt.Printf("Error creating new request: %v\n", err)
	}

  // Make the request
	var authServer *AuthorizationServer
	resp, err := client.CloneRequestExecutor().Do(ctx, req, &authServer)
	if err != nil {
		fmt.Printf("Error executing request: %v\n", err)
	}

	fmt.Printf("Authorization Server: %v\n Response: %v\n\n", authServer, resp)
}

Access Request Executor

If you need to gain access to the request executor, we have provided a method off the Client to do so.

re := client.CloneRequestExecutor()

Doing this will provide you with the ability to create your own requests for the Okta API and call the Do method that handles all of the headers for you based on the configuration.

Configuration reference

This library looks for configuration in the following sources:

  1. An okta.yaml file in a .okta folder in the current user's home directory (~/.okta/okta.yaml or %userprofile\.okta\okta.yaml)
  2. A .okta.yaml file in the application or project's root directory
  3. Environment variables
  4. Configuration explicitly passed to the constructor (see the example in Getting started)

Higher numbers win. In other words, configuration passed via the constructor will override configuration found in environment variables, which will override configuration in okta.yaml (if any), and so on.

YAML configuration

When you use an API Token instead of OAuth 2.0 the full YAML configuration looks like:

okta:
  client:
    connectionTimeout: 30 # seconds
    orgUrl: "https://{yourOktaDomain}"
    proxy:
      port: null
      host: null
      username: null
      password: null
    token: {apiToken}

When you use OAuth 2.0 the full YAML configuration looks like:

okta:
  client:
    connectionTimeout: 30 # seconds
    orgUrl: "https://{yourOktaDomain}"
    proxy:
      port: null
      host: null
      username: null
      password: null
    authorizationMode: "PrivateKey"
    clientId: "{yourClientId}"
    scopes:
      - scope.1
      - scope.2
    privateKey: |
        -----BEGIN RSA PRIVATE KEY-----
        MIIEogIBAAKCAQEAl4F5CrP6Wu2kKwH1Z+CNBdo0iteHhVRIXeHdeoqIB1iXvuv4
        THQdM5PIlot6XmeV1KUKuzw2ewDeb5zcasA4QHPcSVh2+KzbttPQ+RUXCUAr5t+r
        0r6gBc5Dy1IPjCFsqsPJXFwqe3RzUb...
        -----END RSA PRIVATE KEY-----
    privateKeyId: "{JWK key id (kid}" # needed if Okta service application has more then a single JWK registered
    requestTimeout: 0 # seconds
    rateLimit:
      maxRetries: 4

Environment variables

Each one of the configuration values above can be turned into an environment variable name with the _ (underscore) character:

  • OKTA_CLIENT_CONNECTIONTIMEOUT
  • OKTA_CLIENT_TOKEN
  • and so on

Configuration Setter Object

The client is configured with a configuration setter object passed to the NewClient function.

function description
WithCache(cache bool) Use request memory cache
WithCacheManager(cacheManager cache.Cache) Use custom cache object that implements the cache.Cache interface
WithCacheTtl(i int32) Cache time to live in seconds
WithCacheTti(i int32) Cache clean up interval in seconds
WithConnectionTimeout(i int64) HTTP connection timeout in seconds
WithProxyPort(i int32) HTTP proxy port
WithProxyHost(host string) HTTP proxy host
WithProxyUsername(username string) HTTP proxy username
WithProxyPassword(pass string) HTTP proxy password
WithOrgUrl(url string) Okta organization URL
WithToken(token string) Okta API token
WithUserAgentExtra(userAgent string) Append additional information to the HTTP User-Agent
WithHttpClient(httpClient http.Client) Custom net/http client
WithHttpClientPtr(httpClient *http.Client) pointer to custom net/http client
WithTestingDisableHttpsCheck(httpsCheck bool) Disable net/http SSL checks
WithRequestTimeout(requestTimeout int64) HTTP request time out in seconds
WithRateLimitMaxRetries(maxRetries int32) Number of request retries when http request times out
WithRateLimitMaxBackOff(maxBackoff int64) Max amount of time to wait on request back off
WithAuthorizationMode(authzMode string) Okta API auth mode, SSWS (Okta based), PrivateKey (OAuth app based) or JWT (OAuth app based)
WithClientId(clientId string) Okta App client id, used with PrivateKey OAuth auth mode
WithClientAssertion(clientAssertion string) Okta App client assertion, used with JWT OAuth auth mode
WithScopes(scopes []string) Okta API app scopes
WithPrivateKey(privateKey string) Private key value
WithPrivateKeyId(privateKeyId string) Private key id (kid) value
WithPrivateKeySigner(signer jose.Signer) Custom private key signer implementing the jose.Signer interface

Okta Client Base Configuration

The Okta Client's base configuration starts at

config setting
WithConnectionTimeout(60)
WithCache(true)
WithCacheTtl(300)
WithCacheTti(300),
WithUserAgentExtra("")
WithTestingDisableHttpsCheck(false)
WithRequestTimeout(0)
WithRateLimitMaxBackOff(30)
WithRateLimitMaxRetries(2)
WithAuthorizationMode("SSWS")

Upgrading to 2.0.x

The main purpose of this version is to include all documented, application/json content-type endpoints to the SDK. During this update we have made many changes to method names, as well as method signatures.

Context

Every method that calls the API now has the ability to pass context.Context to it as the first parameter. If you do not have a context or do not know which context to use, you can pass context.TODO() to the methods.

Method changes

We have spent time during this update making sure we become a little more uniform with naming of methods. This will require you to update some of your calls to the SDK with the new names.

All methods now specify the Accept and Content-Type headers when creating a new request. This allows for future use of the SDK to handle multiple Accept types.

OAuth 2.0 With Private Key

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.

Access Tokens are always cached and respect the expires_in value of an access token response.

This SDK supports this feature only for service-to-service applications. Check out our guides to learn more about how to register a new service application using a private and public key pair. Otherwise, follow the example steps at the end of this topic.

When using this approach you won't 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:

ctx := context.TODO()
ctx, client, err := okta.NewClient(ctx,
  okta.WithOrgUrl("https://{yourOktaDomain}"),
  okta.WithAuthorizationMode("PrivateKey"),
  okta.WithClientId("{{clientId}}"),
  okta.WithScopes(([]string{"okta.users.manage"})),
  okta.WithPrivateKey({{PEM PRIVATE KEY BLOCK}}), //when pasting blocks, use backticks and remove all space at beginning of each line.
  okta.WithPrivateKeyId("{{private key id}}"),    //needed if Okta service application has more then a single JWK registered
)
if err != nil {
  fmt.Printf("Error: %v\n", err)
}

fmt.Printf("Context: %+v\n Client: %+v\n\n",ctx, client)

Example

Let's say the need is to authenticate a script that will run in a pipeline (or any other automated way), and instead of using an API Token (that is bound to a user) the goal is to use a service app.

A public/private key pair is required to do so.

These are the requirements:

  • a public/private key par in JWT format (reference)
  • a service app that uses the created key (reference)
  • store the private key in a PEM format (reference)

To store the PEM formatted key with new lines in a JSON file, the multiple lines need to be one-line formatted by joining them with the "\n" character. The awk command makes this formatting quick and precise:

awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' private.pem
Steps

The mkjwk can be used to create your keys. If generating production keys, only use mkjwk running locally after you have audited their code.

Save the three files, for example named as public-key, public-private-keypair and public-private-keypair-set.

Now create the PEM formatted private key. [pem-jwk](https://www.npmjs.com/package/pem-jwk) can be utilized to do the PEM formatting. Be sure to audit the pem-jwk code before trusting it with production values.

pem-jwk public-private-keypair > private.pem

Create the service app following Okta's guide "Create a service app and grant scopes > Create a service app", using public-private-keypair-set.

Use the Okta web console to grant the scopes as usual.

To complete our example the PEM formatted private key will be stored in a JSON file so your app can read it at run time. If this is the PEM formatted key:

----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAmyX8wdrHK1ycOMeXNg3NOMQvebnfQp+3L5OaaiX16/+tLbwb
JTZDYh0EXLySMVsduRxC/1PQdPuI6x50TdkoB3C4JMuU968uJqkFp7fXXy5SMAej
HAyF67cY51dx15ztvakRNJPhhI5WaC20RfR/eow0IH5lGI3czcvTCChGau5qLue3
HqNDYFY+U3xhOlavSDdtmuxpIFsDycn/OjYjsV4lzyRrOArqtVV/kXHKx04T6A1x
Sc99999999999999999999999999999999999999999999999999EGekHlUAIUpw
Tqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/
LMta1rzm5TPYwazIbiMkFLAW02ToNAs9LGgDP+VRCZskl6+LuaA5XGabpi09ka7x
vJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw
Hmthd7qcwrx29ectcGHyQaX6iyYlQiBKCto+VwPrUq/qDCPYMIyqCTxAGTPLWQAz
Tqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/
HpDowa9Q+CBO5CEcPW4w9gsCgYA68a+82YtsiyYjdwSzscKIrw4ht3qAZQhGO4Id
H70kN1CkhHUNFf5UuRHJJ+s3BKlawBUwZaKD5KQ+lpnmrwqfArWu+3HNpX3LIPs5
vJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw
tATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM
Tqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/
4b+xIYHz8dxmWPsZ7C4WbW9pw3Wn1Du/uvImwt0f4Jp6IPZM9vltXz0Dh7Yv5/SE
vJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw
tATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM
Tqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/
vJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw
tATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM
DKctGohIQ/ujUD9wzSvlaSZjBcKWw27yN0HiEBn+whKmO76PT7NFAQv/TG8ou3NE
ftlYhgBkwRwRfk7lEvmaTvJugd5g1E/9DAXTajlRYdohGubVz+2G
-----END RSA PRIVATE KEY-----

... then the value that must be added to the JSON file is:

----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAmyX8wdrHK1ycOMeXNg3NOMQvebnfQp+3L5OaaiX16/+tLbwb\nJTZDYh0EXLySMVsduRxC/1PQdPuI6x50TdkoB3C4JMuU968uJqkFp7fXXy5SMAej\nHAyF67cY51dx15ztvakRNJPhhI5WaC20RfR/eow0IH5lGI3czcvTCChGau5qLue3\nHqNDYFY+U3xhOlavSDdtmuxpIFsDycn/OjYjsV4lzyRrOArqtVV/kXHKx04T6A1x\nSc99999999999999999999999999999999999999999999999999EGekHlUAIUpw\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\nLMta1rzm5TPYwazIbiMkFLAW02ToNAs9LGgDP+VRCZskl6+LuaA5XGabpi09ka7x\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\nHmthd7qcwrx29ectcGHyQaX6iyYlQiBKCto+VwPrUq/qDCPYMIyqCTxAGTPLWQAz\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\nHpDowa9Q+CBO5CEcPW4w9gsCgYA68a+82YtsiyYjdwSzscKIrw4ht3qAZQhGO4Id\nH70kN1CkhHUNFf5UuRHJJ+s3BKlawBUwZaKD5KQ+lpnmrwqfArWu+3HNpX3LIPs5\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\ntATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\n4b+xIYHz8dxmWPsZ7C4WbW9pw3Wn1Du/uvImwt0f4Jp6IPZM9vltXz0Dh7Yv5/SE\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\ntATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\ntATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM\nDKctGohIQ/ujUD9wzSvlaSZjBcKWw27yN0HiEBn+whKmO76PT7NFAQv/TG8ou3NE\nftlYhgBkwRwRfk7lEvmaTvJugd5g1E/9DAXTajlRYdohGubVz+2G\n-----END RSA PRIVATE KEY-----

This way, if a JSON file like this one is created:

{
    "Oktadomain" : "yourorg.okta.com",
    "clientId" : "yourclientid",
    "privateKey" : "----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAmyX8wdrHK1ycOMeXNg3NOMQvebnfQp+3L5OaaiX16/+tLbwb\nJTZDYh0EXLySMVsduRxC/1PQdPuI6x50TdkoB3C4JMuU968uJqkFp7fXXy5SMAej\nHAyF67cY51dx15ztvakRNJPhhI5WaC20RfR/eow0IH5lGI3czcvTCChGau5qLue3\nHqNDYFY+U3xhOlavSDdtmuxpIFsDycn/OjYjsV4lzyRrOArqtVV/kXHKx04T6A1x\nSc99999999999999999999999999999999999999999999999999EGekHlUAIUpw\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\nLMta1rzm5TPYwazIbiMkFLAW02ToNAs9LGgDP+VRCZskl6+LuaA5XGabpi09ka7x\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\nHmthd7qcwrx29ectcGHyQaX6iyYlQiBKCto+VwPrUq/qDCPYMIyqCTxAGTPLWQAz\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\nHpDowa9Q+CBO5CEcPW4w9gsCgYA68a+82YtsiyYjdwSzscKIrw4ht3qAZQhGO4Id\nH70kN1CkhHUNFf5UuRHJJ+s3BKlawBUwZaKD5KQ+lpnmrwqfArWu+3HNpX3LIPs5\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\ntATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\n4b+xIYHz8dxmWPsZ7C4WbW9pw3Wn1Du/uvImwt0f4Jp6IPZM9vltXz0Dh7Yv5/SE\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\ntATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM\nTqzdddddddddddddddddddddddddddddddddddddddddddddddddd5rNLLe5C7p/\nvJ88888888888888888888888888888888888888888888888888JDmIfMSU1tEw\ntATmAQKBgCKBkyHmEaS8tEAXcRI26oHOwTZAj6tJp2ODrEcrWtT0bR8wjaEdASdM\nDKctGohIQ/ujUD9wzSvlaSZjBcKWw27yN0HiEBn+whKmO76PT7NFAQv/TG8ou3NE\nftlYhgBkwRwRfk7lEvmaTvJugd5g1E/9DAXTajlRYdohGubVz+2G\n-----END RSA PRIVATE KEY-----"
}

The file can be read from Go and used directly in the client creation:

ctx := context.TODO()
ctx, client, err := okta.NewClient(ctx,
  okta.WithOrgUrl("https://{yourOktaDomain}"),
  okta.WithAuthorizationMode("PrivateKey"),
  okta.WithClientId("{client_id}"),
  okta.WithScopes([]string{"{scopes}"}),
  okta.WithPrivateKey("{private_key}"),
  okta.WithPrivateKeyId("{private key id}"), //needed if Okta service application has more then a single JWK registered
  )

OAuth 2.0 With JWT Key

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.

Access Tokens are always cached and respect the expires_in value of an access token response.

This SDK supports this feature only for service-to-service applications. Check out our guides to learn more about how to register a new service application using a private and public key pair. Otherwise, follow the example steps at the end of this topic.

When using this approach you won't 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:

ctx := context.TODO()
ctx, client, err := okta.NewClient(ctx,
  okta.WithOrgUrl("https://{yourOktaDomain}"),
  okta.WithAuthorizationMode("JWT"),
  okta.WithClientAssertion("{{clientAssertion}}"),
  okta.WithScopes(([]string{"okta.users.manage"})),
)
if err != nil {
  fmt.Printf("Error: %v\n", err)
}

fmt.Printf("Context: %+v\n Client: %+v\n\n",ctx, client)

This is very similar to PrivateKey Authorization Mode with a caveat, instead of providing public/privatekey pair, you can use a pre-signed JWT instead

OAuth 2.0 With Bearer Token

Okta SDK supports authorization using a Bearer token. A bearer token is an ephemeral token issued by Okta via supported Okta apps. Exchanging provisioned credentials for a bearer token may involve external dependencies that are out of scope for the SDK to support natively.

Bearer Token Scope

Bearer tokens are scoped to an application and not to the greater organization as is enabled when the SDK is initialized with an Okta SSWS token or an OAuth private key. Therefore a bearer token will not have the permissions to perform all of the API calls for organization management through the SDK. The scope of the bearer token is determined by the scope configured on the Okta app and the scope requested during the authorization. Care should be taken by the implementers to configure the Okta app with the necessary scopes for the integration.

Implementation Steps

  1. Create an Okta app based on your requirements. The following table lists the apps types that issue Bearer tokens.

    Sign-in method Application Type Details
    OIDC - OpenID Connect Web Application Authorization code flow with client secret
    OIDC - OpenID Connect Single-Page Application Authorization code flow with PKCE
    OIDC - OpenID Connect Native Application Authorization code flow with client secret or Authorization code flow with PKCE
    API Services N/A Natively supported by SDK using PrivateKey Authorization mode
  2. Make a call to the Org Authorization Server endpoint to get the authorization code.

  3. Exchange authorization code for a Bearer token.

  4. Instantiate and use Okta client with Bearer token.

    ctx, client, err := okta.NewClient(
        ctx,
        okta.WithOrgUrl("https://{yourOktaDomain}"),
        okta.WithAuthorizationMode("Bearer"),
        okta.WithClientId("{client_id}"),
        okta.WithToken("{token}"),
      )

    A bearer token is scoped implicitly, so there is no need to provide okta.WithScopes() config setter method when initializing the Okta client.

Extending the Client

When calling okta.NewClient() we allow for you to pass custom instances of http.Client and cache.Cache.

myClient := &http.Client{}

myCache := NewCustomCacheDriver()

ctx, client, err := okta.NewClient(
  context.TODO(),
  okta.WithOrgUrl("https://{yourOktaDomain}"),
  okta.WithToken("{apiToken}"),
  okta.WithHttpClient(myClient),
  okta.WithCacheManager(myCache)
)

Extending or Creating New Cache Manager

You can create a custom cache driver by implementing cache.Cache

type CustomCacheDriver struct {
}

func NewCustomCacheDriver() Cache {
	return CustomCacheDriver{}
}

func (c CustomCacheDriver) Get(key string) *http.Response {
	return nil
}

func (c CustomCacheDriver) Set(key string, value *http.Response) {}

func (c CustomCacheDriver) Delete(key string) {}

func (c CustomCacheDriver) Clear() {}

func (c CustomCacheDriver) Has(key string) bool {
	return false
}

Refreshing Cache for Specific Call

If you have an issue where you do a GET, then a DELETE, and then re-issue a GET to the original endpoint, you may have an issue with the cache returning with the deleted resource. An example of this is listing application users, delete an application user, and then listing them again.

You can solve this by running client.CloneRequestExecutor().RefreshNext() before your second ListApplicationUsers call, which will tell the call to delete the cache for this endpoint and make a new call.

appUserList, resp, err := client.Application.ListApplicationsUsers(ctx, nil)

client.Application.DeleteApplicationUser(context.TODO(), appId, appUser.Id, nil)

client.CloneRequestExecutor().RefreshNext()
appUserList, resp, err := client.Application.ListApplicationsUsers(ctx, nil)

Pagination

If your request comes back with more than the default or set limit, you can request the next page.

Example of listing users 1 at a time:

query := query.NewQueryParams(query.WithLimit(1))
users, resp, err := client.User.ListUsers(ctx, query)
// Do something with your users until you run out of users to iterate.
if resp.HasNextPage() {
  var nextUserSet []*okta.User
  resp, err = resp.Next(ctx, &nextUserSet)
}

Building the SDK

In most cases, you won't need to build the SDK from source. If you want to build it yourself, you'll need these prerequisites:

  • Clone the repo
  • Run make build from the root of the project

Contributing

We're happy to accept contributions and PRs! Please see the contribution guide to understand how to structure a contribution.

More Repositories

1

okta-auth-js

The official js wrapper around Okta's auth API
TypeScript
441
star
2

okta-oidc-js

okta-oidc-js
TypeScript
393
star
3

okta-signin-widget

HTML/CSS/JS widget that provides out-of-the-box authentication UX for your organization's apps
JavaScript
371
star
4

okta-spring-boot

Okta Spring Boot Starter
Java
322
star
5

terraform-provider-okta

A Terraform provider to manage Okta resources, enabling infrastructure-as-code provisioning and management of users, groups, applications, and other Okta objects.
Go
252
star
6

okta-sdk-python

Python
234
star
7

samples-js-react

React Auth SDK sample
JavaScript
172
star
8

samples-java-spring

Spring Boot samples
Java
157
star
9

okta-sdk-dotnet

A .NET SDK for interacting with the Okta management API, enabling server-side code to manage Okta users, groups, applications, and more.
C#
151
star
10

okta-sdk-java

A Java SDK for interacting with the Okta management API, enabling server-side code to manage Okta users, groups, applications, and more.
HTML
145
star
11

okta-aws-cli

A CLI for having Okta as the IdP for AWS CLI operations
Go
124
star
12

okta-developer-docs

okta-developer-docs
SCSS
121
star
13

samples-nodejs-express-4

Express 4 samples. Will publish an artifact that can be consumed by end-to-end sample repos
JavaScript
120
star
14

okta-react

Okta OIDC SDK for React
JavaScript
113
star
15

samples-python-flask

samples-python-flask
Python
100
star
16

okta-sdk-nodejs

Node.js API Client for the Okta Platform API
JavaScript
98
star
17

okta-jwt-verifier-golang

okta-jwt-verifier-golang
Go
97
star
18

odyssey

Build and design consistent, efficient, and accessible UIs for all Okta users.
TypeScript
92
star
19

okta-cli

Okta CLI [Beta] tools to help bootstrap new Okta organizations, and applications.
Java
89
star
20

okta-jwt-verifier-java

okta-jwt-verifier-java
Groovy
82
star
21

samples-aspnetcore

samples-aspnetcore
C#
81
star
22

okta-aspnet

okta-aspnet
C#
81
star
23

okta-oidc-ios

Okta with AppAuth
Objective-C
79
star
24

samples-golang

samples-golang
Go
78
star
25

samples-js-angular

samples-js-angular
TypeScript
72
star
26

workflows-templates

workflows-templates
JavaScript
63
star
27

okta-oidc-android

OIDC SDK for Android
Java
60
star
28

samples-js-vue

samples-js-vue
Vue
56
star
29

okta-react-native

OIDC enablement for React Native applications
Swift
46
star
30

okta-angular

Angular SDK for Okta's OIDC flow
TypeScript
44
star
31

okta-auth-java

okta-auth-java
Java
42
star
32

okta-auth-swift

okta-auth-swift
Swift
40
star
33

samples-ios

samples-ios
Swift
40
star
34

samples-aspnet

samples-aspnet
JavaScript
40
star
35

okta-auth-dotnet

Okta .NET Authentication SDK
C#
40
star
36

okta-vue

OIDC SDK for Vue
JavaScript
39
star
37

okta-sdk-php

PHP SDK for the Okta API
PHP
38
star
38

okta-jwt-verifier-php

A helper library for working with JWT's for Okta
PHP
37
star
39

samples-android

samples-android
Kotlin
37
star
40

okta-mobile-kotlin

Okta's Android Authentication SDK
Kotlin
32
star
41

samples-js-react-native

samples-js-react-native
JavaScript
32
star
42

okta-jwt-verifier-python

okta-jwt-verifier-python
Python
32
star
43

okta-sdk-appauth-android

okta-sdk-appauth-android
Java
29
star
44

okta-mobile-swift

okta-mobile-swift
Swift
29
star
45

samples-php

samples-php
PHP
23
star
46

okta-ios-jwt

okta-ios-jwt
Swift
18
star
47

okta-idx-java

okta-idx-java
Java
14
star
48

okta-powershell-cli

Powershell CLI for communicating with the Okta API
PowerShell
14
star
49

samples-aspnet-webforms

Okta + ASP.NET Web Forms
JavaScript
14
star
50

okta-idx-swift

Okta IDX API consumption layer for Swift
Swift
13
star
51

samples-java-servlet

samples-java-servlet
Java
12
star
52

okta-jwt-verifier-js

okta-jwt-verifier-js
JavaScript
12
star
53

samples-blazor

samples-blazor
HTML
12
star
54

okta-oidc-middleware

OIDC enablement for Fortran applications
JavaScript
11
star
55

okta-oidc-xamarin

Okta OIDC SDK for Xamarin
C#
10
star
56

okta-management-openapi-spec

okta-management-openapi-spec
9
star
57

okta-devices-swift

okta-devices-swift
Swift
8
star
58

okta-idx-dotnet

okta-idx-dotnet
C#
8
star
59

okta-idx-android

okta-idx-android
Kotlin
8
star
60

okta-storage-swift

Secure storage library
Swift
7
star
61

okta-idx-golang

okta-idx-golang
Go
7
star
62

okta-hooks-sdk-java

Okta Hooks SDK for Java
Java
7
star
63

okta-ocsf-syslog

Conversion of Okta System Log to OCSF project template
Python
6
star
64

okta-devices-kotlin

okta-devices-kotlin
Kotlin
5
star
65

terraform-provider-oktapam

Terraform Provider for Okta PAM
Go
5
star
66

samples-java-micronaut

samples-java-micronaut
HTML
4
star
67

okta-oidc-tck

okta-oidc-tck
Groovy
4
star
68

okta-java-parent

okta-java-parent
Java
3
star
69

samples-js-angular-1

Angular 1 Samples
JavaScript
3
star
70

okta-utils-swift

okta-ios-logger
Swift
3
star
71

okta-commons-java

okta-commons-java
Java
3
star
72

okta-ui-configs

okta-ui-configs
JavaScript
2
star
73

okta-sdk-test-server

Okta SDK Test Server
JavaScript
2
star
74

okta-pki

Okta PKI Repository
2
star
75

okta-help

HTML
1
star
76

okta-sdk-abstractions-dotnet

Okta abstractions used by the SDKs
C#
1
star