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.
Objectives
- OAuth2 Flow
- IAM Scopes
- JWT Support
- Organization Ownership
Authorization Grant Types
OAuth2 defines four grant types, which can be used in different cases:
- Authorization Code: used with server-side Applications.
- Implicit: used with Mobile Applications or Web Applications (applications that run on the user's device).
- Resource Owner Password: used with trusted Applications, such as those owned by the service itself.
- 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.
Step 1 : Authorization Code Link
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:
- An application linked to an organization to access its own account.
- 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:
- Use an OAuth2 token for JWT creation where the JWT's claim set is a subset of the OAuth token's scopes.
- Directly get a JWT instead of a normal OAuth token when following the OAuth2 grant type flows.
- 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 aglobalid
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
ororganization: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 isapplication/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 oftoken
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 arefresh_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:
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.