Skip to content

IAM In Depth

Identity and Access Management (IAM) lets users authorize who can take actions on specific resources, giving you full control and visibility to manage your cloud services which make them more secure.

Identity & Access Management Diagram

Objectives

  1. OAuth2 Flow
  2. IAM Scopes
  3. JWT Support
  4. Organization Ownership

Authorization Grant Types

OAuth2 defines four grant types, which can be used in different cases:

  1. Authorization Code: used with server-side Applications.
  2. Implicit: used with Mobile Applications or Web Applications (applications that run on the user's device).
  3. Resource Owner Password: used with trusted Applications, such as those owned by the service itself.
  4. Client Credentials: used with Applications API access.

Currently, the Authorization Code and Client Credentials grant types are supported.

Authorization Code Flow

The authorization code grant type is the most commonly used because it is optimized for server-side applications where Client Secret confidentiality can be maintained. This is a redirection-based flow, which means that the application must be capable of interacting with the user-agent (i.e. the user's web browser) and receiving API authorization codes that are routed through the user-agent.

Client ID and Client Secret

To acquire an OAuth access token, a Client ID and Client Secret are required.

In iam.whitesky.cloud, organizations map to clients in the OAuth2 terminology and the organization's globalid is used as the clientid. Client Secrets can be created through the UI or the organizations/{globalid}/apikeys API.

IAM Authorization Code Flow

First, the user is given an authorization code link that looks like this:

https://iam.whitesky.cloud/v1/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=user:name&state=STATE
  • https://iam.whitesky.cloud/v1/oauth/authorize: the API authorization endpoint.
  • client_id = client_id (The application's Client ID).
  • redirect_uri = CALLBACK_URL (The redirect_uri parameter is required. The redirect URL's host and port must exactly match the callback URL and the redirect URL's path must reference a subdirectory of the callback URL. The redirect_uri must start with a scheme indicator (scheme://)).
  • response_type = code (Specifies that your application is requesting an authorization code grant).
  • scope = user:name (Specifies the level of access that the application is requesting, here we specify the scope "user:name", see the available scopes for all other supported scopes).
  • state = STATE (A random string. It is used to protect against CSRF Attacks).

Step 2 : User Authorizes Application

When the user clicks on the link, they must first log in to the service, to authenticate their identity. Then they will be prompted by the service to authorize or deny the application access to the requested information.

Step 3 : Application Receives Authorization Code

After the user authorizes the application, iam.whitesky.cloud redirects the user-agent to the application redirect URI, which was specified during the client registration, along with an authorization code and a state parameter passed in Step 1. If the state parameters don't match, the request has been created by a third-party and the process should be aborted.

The redirect would look something like this (assuming the application is "iam.whitesky.cloud):

https://iam.whitesky.cloud/callback?code=AUTHORIZATION_CODE&state=STATE

This code is only valid for 10 seconds, so an access token should be requested immediately after the callback is received.

Step 4 : Application Requests Access Token

The application requests an access token from the API, by passing the authorization code along with authentication details, including the Client Secret, to the API token endpoint and the state.

Here is an example POST request to iam.whitesky.cloud's token endpoint:

POST https://iam.whitesky.cloud/v1/oauth/access_token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL&state=STATE

Note: Alternatively one can pass the client_id and client_secret via basic authentication header and remove them from the post data.

The redirect_uri must match the redirect_uri passed in the access_code request and the callback URI registered in the API key. The redirect URL's host and port must exactly match the callback URL and the redirect URL's path must reference a subdirectory of the callback URL. The state must match the state received with the authorization code.

  • response_type = code

Step 5 : Application Receives Access Token

If the authorization is valid, the API will send a response containing the access token (and optionally, a refresh token) to the application.

The entire response will look like this:

{"access_token":"ACCESS_TOKEN","token_type":"bearer","expires_in":86400,"refresh_token":"REFRESH_TOKEN","scope":"read","info":{"username":"bob"}}

Now the application is authorized. It may use the token to access the user's account via the service API, limited to the scope of access until the token expires or is revoked. If a refresh token was issued, it may be used to request new access tokens if the original token has expired.

Use Access Token to Access the API

The access token allows you to make requests to the API on behalf of a user.

GET https://iam.whitesky.cloud/api/users/bob/info?access_token=...

You can pass the token in the query parameters like shown above, but a cleaner approach is to include it in the Authorization header.

Authorization: token OAUTH-TOKEN

For example, in curl you can set the Authorization header like this:

curl -H "Authorization: token OAUTH-TOKEN" https://iam.whitesky.cloud/api/users/bob/info

Client Credentials Flow

The Client Credentials Grant Types can be used in two scenarios:

  1. An application linked to an organization to access its own account.
  2. An application linked to a user to access the user's account.

Examples of when this might be useful include if an application wants to invite someone to an organization or access other organization data using the API.

Client ID and Client Secret

To acquire an OAuth Access Token, a Client ID and Client Secret are required.

Organization API Key

In iam.whitesky.cloud, organizations map to clients in the OAuth2 terminology and the organization's globalid is used as the client_id. Client Secrets can be created through the UI by going to the organization's settings page or through the api/organizations/{globalid}/apikeys API.

Note: To use an APIkey in a client credentials flow, enable client credentials flow must be set on the APIkey (through the API or in the APIkey detail dialog in the UI).

The global ID of the organization is a unique identifier of the organization. For root organizations global ID is simply the name of the organization. For sub-organizations, the global ID contains the organization name prepended by all the parent organization names, for example:

root-organization.suborganization1.suborganization2.your-organization.

User API Key

It is possible to create API keys to access a user's data through the API instead of the UI.

  • In the UI, create a new API key in the user's settings page. This will generate an application id and a secret. Use the application id as client_id and the secret as client_secret.
  • API keys can also be created through the api/users/{username}/apikeys API or by creating a user through the API ( POST on /api/users).

Acquire Access Token

The application requests an access token by sending its credentials, its client_id and client_secret, to the authorization server.

An example POST request might look like this:

https://iam.whitesky.cloud/v1/oauth/access_token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
  • client_id = CLIENT_ID (The organization's globalid or the application id as described above).
  • client_secret = CLIENT_SECRET (The API key secret).
  • grant_type = client_credentials (Specifies that your application is requesting an access token using the client credentials flow).

Note: If the application credentials check out, the authorization server returns an access token to the application.

Use the Access Token to Access the API

The access token allows you to make requests to the API as described in the Authorization Code Grant Type. When an organization API key is used, the requests are on behalf of the organization instead of on behalf of a user.

Identity and Access Management Scopes

Scopes work a bit differently in most known OAuth2 Identity Providers since we want to have more control about which information is exposed to client applications.

Clarification Scenario

Bob wants to login to his iam.whitesky.cloud account and Bob authenticates using iam.whitesky.cloud. iam.whitesky.cloud asks for a username and an email address and asks for the scope user:info:username, user:info:emailaddress during the authorization flow as described in the Authorization Code Grant Type.

bob:
  info:
    username:
      ...
    emailaddress:
      ...
    fullname:
      ...

During the authorization request, iam.whitesky.cloud informs Bob that it needs his username and email address.

This scope mapping is saved as such and when iam.whitesky.cloud requests the user information on /users/bob/info using the OAuth token acquired, the following information will be returned:

{
    "name":"bob",
    "info":
        [
            {"username":{...}},
            {"emailaddress":{...}}
        ]
}

The same concept is applied to all labeled user properties.

Types of Scopes

User Have Admin Scope

Path Description
/users/{username} {username} is a user on which the user:admin scope is granted: user:admin
/organizations/{globalid} User is the owner of the organization organization:owner or User is a member of the organization organization:member

Requesting Scopes by OAuth Client

Path Description
user:name First and last name of the user
user:memberof: A client can check if a user is member or owner of an organization with a specific globalid. iam.whitesky.cloud checks if the user is indeed member or owner and the user needs to confirm that the requesting client is allowed to know that he/she is part of the organization. If the user is not a member of the organization, the OAuth flow continues but the scope will not be available. (This scope can be requested multiple times)
user:address[: User Address
user:email[: User Email
user:phone[: The :write extension gives an application full access(read, update, delete) to a phone number
user:publickey[: User Public Key
user:ownerof:email: Users need to share this verified email address to complete the authorization flow. If a user registers during an OAuth flow where this scope is requested, the email address is automatically filled in the registration screen.
user:ownerof:organization: Owner of Organization

JSON Web Token (JWT) Support

Even though the OAuth token support works great for applications that need to access the information of a user, when passing on some of these authorizations to a third-party service it is not a good idea to pass on the token itself.

The token you acquired might give access to a lot more information than that what you want to pass on to the third-party service and it is required to invoke iam.whitesky.cloud to verify that the authorization claim is valid.

For these use cases, iam.whitesky.cloud supports JWT RFC7519.

Refreshing JWT

The standard OAuth2 specification declares a refresh_token as a kind of special API key that is returned upon getting the OAuth token or ID token. While this allows getting an entirely new token with the original scopes, it does have some drawbacks:

  • The refresh token needs to be stored separately and if authorizations are passed to third-party systems, the refresh token needs to be passed along as well if they are long-running services.
  • No way of limiting the scope set when passing a refresh token to someone else.

iam.whitesky.cloud puts the refresh token in the JWT itself, allowing to refresh the token without separating the refresh token. To include a refresh token in a JWT, one should ask for the offline_access scope. A refresh_token claim is inserted in the returned JWT. To refresh it, just call /v1/oauth/jwt/refresh with the expired JWT as a bearer token in the Authorization header and you get a new one if the authorization still stands.

curl -H "Authorization: bearer OLD-JWT-TOKEN" https://iam.whitesky.cloud/v1/oauth/jwt/refresh

If some of the authorizations for this token were removed, they are no longer returned in the scopes of the newly generated JWT.

If a refresh token has not been used for more than 30 days it will no longer be valid.

Acquiring a JWT

iam.whitesky.cloud supports several ways of obtaining JWTs:

  1. Use an OAuth2 token for JWT creation where the JWT's claim set is a subset of the OAuth token's scopes.
  2. Directly get a JWT instead of a normal OAuth token when following the OAuth2 grant type flows.
  3. Create a new JWT by using an existing JWT as authentication/authorization.

Case 1: Use an OAuth2 token for JWT creation where the JWT's claim set is a subset of the OAuth token's scopes

Suppose you have an OAuth token OAUTH-TOKEN with the following scopes:

  • user:memberOf:org1
  • user:memberOf:org2
  • user:address:billing

and you want to call a third-party service that only needs to know if the user is a member of org1, there is no need to expose the billing address you are authorized to see.

You can create a JWT like this:

curl -H "Authorization: token OAUTH-TOKEN" https://iam.whitesky.cloud/v1/oauth/jwt?scope=user:memberof:org1

The scope parameter can be a comma separated list of scopes. Instead of a query parameter, an HTTP POST can also be submitted to this URL with the scope parameter as a form value.

The response will be a JWT with:

  • Header
  {
    "alg": "ES384",
    "typ": "JWT"
  }
  • Data
  {
    "username": "bob",
    "scope": "user:memberof:org1",
    "iss": "iam.whitesky.cloud",
    "aud": ["CLIENTID"],
    "exp": 1463554314
  }
  • iss: Issuer, in this case "iam.whitesky.cloud".
  • exp: Expiration time in seconds since the epoch. This is set to the same time as the expiration time of the OAuth token used to acquire this JWT.
  • aud: An array with at least 1 entry: the client_id of the OAuth token used to acquire this JWT.
  • If the OAuth token is not for a user but for an organization application that is authenticated using the client credentials flow, the username field is replaced with a globalid field containing the globalid of the organization.

  • Signature

The JWT is signed by iam.whitesky.cloud and the public key to verify if this JWT was really issued by iam.whitesky.cloud is:

 -----BEGIN PUBLIC KEY-----
 MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAES5X8XrfKdx9gYayFITc89wad4usrk0n2
 7MjiGYvqalizeSWTHEpnd7oea9IQ8T5oJjMVH5cc0H5tFSKilFFeh//wngxIyny6
 6+Vq5t5B0V0Ehy01+2ceEon2Y0XDkIKv
 -----END PUBLIC KEY-----

In case the requested scopes are not available for your OAuth token or the token has expired, an HTTP 401 status code is returned.

Case 2: Directly Get a JWT Instead of a Normal OAuth2 Token (OAuth2 Grant Type Flows)

When using one of the authorization flows explained in the Authorization grant types, it is also possible to directly get a JWT returned instead of an OAuth2 token itself.

Add the response_type=id_token and a scope parameter with the desired scopes to the /v1/oauth/access_token call to do this. For example:

https://iam.whitesky.cloud/v1/oauth/access_token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&response_type=id_token&scope=user:memberof:org1&aud=external1
  • In this case, the scope parameter needs to be given to prevent consumers from accidentally handing out user:admin or organization:owner scoped tokens to third-party services.

If the request has application/json in the accept header, the response is a JSON structure containing the JWT:

{
    "access_token":"ABCDEFGH........ABCDEFGH"
}
  • If no application/json is present in the accept header, the mime-type is application/jwt and the response is the JWT itself.

Case 3: Create a New JWT using an Existing JWT as Authentication/Authorization

When you have a JWT and want to create a new one with less scopes or remove the refresh_token (or a combination of these), the same call can be performed as in Case 1 but with the JWT in the Authorization Header instead of the Access Token.

curl -H "Authorization: bearer ABCDEFGH........ABCDEFGH" https://iam.whitesky.cloud/v1/oauth/jwt?scope=user:memberof:org1

Note: Be sure to use bearer instead of token in the Authorization Header.

  • If the supplied JWT has a refresh_token, the newly generated JWT has a fresh expiration time, regardless of the expiration time of the supplied JWT. If not, the expiration time of the newly generated JWT is set to the expiration time of the supplied JWT.

  • If a JWT with less scopes is created and the offline_access scope is requested, iam.whitesky.cloud keeps a reference to the parent JWT's authorization and this in effect creates a tree of refreshable authorizations. If a specific authorization is removed from a parent, it is removed from all children as well.

Note: It is not possible to create a JWT with a refresh_token using a JWT that does not have a refresh_token.

Consumers should be careful not to pass JWT's with a refresh_token to third-party services since they can keep using this authorization for as long as the consumer's authorization is valid. When passing a JWT to an external service, it is best to ask for a new JWT first and pass that one on.

Organization Ownership

The example assumes the following Organization Structure:

IAM Organization Ownership

Owners of a parent organization are inherited in sub-organizations.

People that are in the owners list of CustomerOrganization are also considered to be the owner of the CustomerOrganization.Admins and CustomerOrganization.Users organizations without being explicitly in the owner list of those sub-organizations.