In the beginning, there were proprietary approaches to working with external identity providers for authentication and authorisation.
Then SAML (Security Assertion Markup Language) was designed as an open standard using SOAP and XML as its data format.
Then, there was OAuth and OAuth 2.0 using a RESTful approach to authorisation using JSON as its data format instead.
Today, the preferred way of dealing with “secure delegated access” is OpenID Connect (a.k.a OIDC).
OpenID Connect is an identity layer running on top of the OAuth 2.0 protocol, that specifies a RESTful HTTP API, using JSON as a data format.
It allows computing clients to verify the identity of an end-user based on the authentication performed by an authorisation server, as well as to obtain end-user profile information.
OAuth 2.0 describes a number of grants (“methods”) for a client application to acquire an access token (which represents a user’s permission for the client to access their data) which can be used to authenticate a request to an API endpoint.
The specification describes five grants for acquiring an access token.
Onix has been tested with the two flows described below.
The Authorisation Code Grant Type is probably the most common of the OAuth 2.0 grant types as it is used by both web and native applications to get an access token after a user authorises the application.
The flow has the following steps:
- The application opens a browser to send the user to the OAuth server
- The user sees the authorisation prompt and approves the application's request
- The user is redirected back to the application with an authorisation code in the query string
- The application exchanges the authorisation code for an access token
This flow is seamless to the user when using the Onix Web API for example opening the Swagger UI.
As the user hits the Swagger User Interface is redirected to the configured authorisation server. Upon entering their credentials and authenticating successfully, the user is redirected to the Swagger UI.
For more information on how thos flow works see here.
This flow is typically used by client applications which do not want to use a web browser, for example, when talking to the CMDB using Ansible modules or the Terraform provider.
The flow has the following steps:
-
The client application asks the user for their username and password.
-
The client then sends a POST request with following body parameters to the authorisation server:
- grant_type with the value password
- client_id with the the client’s ID
- client_secret with the client’s secret
- scope with a space-delimited list of requested scope permissions.
- username with the user’s username
- password with the user’s password
For example, as follows:
curl --request POST \
--url https://dev-447786.okta.com/oauth2/default/v1/token \
--header 'accept: application/json' \
--header 'Authorization: Basic MG9hbHloMHJ5U1J...' \
--header 'cache-control: no-cache' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'grant_type=password&[email protected]&password=dhgcrce..&scope=openid%20onix'
NOTE: in order to authenticate the request, a basic access token is passed in the authorization header containing the resource server client_id and secret.
-
The authorisation server then responds with a JSON object containing the following properties:
- token_type with the value Bearer
- expires_in with an integer representing the TTL of the access token
- access_token the access token itself
- refresh_token a refresh token that can be used to acquire a new access token when the original expires
{
"access_token":"eyJraWQiOiJuWlVs...dfg",
"token_type":"Bearer",
"expires_in":3600,
"scope":"onix openid",
"id_token":"eyJra...ZOSUZrVGztg"
}
The access_token can be decoded using the jwt.io web site resulting in something like:
{
"ver": 1,
"jti": "AT.C6c9uWz4...WfQ...lJ5IAr1LJ...M",
"iss": "https://dev-447786.okta.com/oauth2/default",
"aud": "api://default",
"iat": 15...7858,
"exp": 15...1458,
"cid": "0oalyh...gaj356",
"uid": "00ujnm...zmY356",
"scp": [
"onix",
"openid"
],
"sub": "[email protected]",
"roles": "ADMIN, WRITER"
}
NOTE: in the example, the token contains a roles claim with two roles ADMIN and WRITER. Onix maps these roles to the privilieges set in its database granting the user access accordingly.
- Finally, the client can then send the bearer token with every request to the Web API, for example to retrieve item types calling the web api as follows:
curl --request GET \
--url http://localhost:8080/itemtype \
--header 'accept: application/json' \
--header 'authorization: Bearer eyJraWQiOiJuWlVs...dfg'
In order to authenticate using an OpenId Connect provider, a client application and a authorisation server must be created in the Identity provider first.
As an example, in Okta do the following:
To create a new Application:
- Go to Applications
- Add Application
- Select "Native" and click "Next".
- Give it a name
- Tick the "Resource Owner Password" box under Grant Type Allowed
- Click done
Now the client_id and secret for the application can be retrieved from the Application General Tab.
The default resource server in Okta can be used.
- Go to API and clieck on Authorisation Servers
- Click on the "default" server link
- Click on "Scopes" and add a new scope called onix
- Click on "Claims" and add a new claim called roles
- Select "include claim in access token"
- Select "Value Type = Expression"
- Type String.toUpperCase("admin, writer") as value
- Under Include in section select "The following scopes"
- Type onix as the scope
In addition, the following environment variables should be specified when launching the Onix WAPI application:
Variable | Description |
---|---|
AUTH_MODE | The authentication mode used by the Onix Web API, must be set it to "oidc". |
OIDC_ISSUER | The name of the token issuer. |
OIDC_JWKURL | The URI whwre the issuer has published the JavaScript Web Key certificates used to validate Java Web Tokens. |
OIDC_REDIRECT_URI | The resource server endpoint prfroming the login. Note: in the Onix Web API the endpoint is "/oidc-login". |
OIDC_USER_AUTH_URI | The URI of the authorisation server. |
OIDC_ACCESS_TOKEN_URI | The URI of the token resource. |
OIDC_CLIENT_SECRET | The client secret used to authenticate with the authorisation server. |
OIDC_CLIENT_ID | The unique identifier for the client application to be protected. |
Note: client id and secret values are created when setting up a new client application in the OpenId provider.
The following values apply to a google provider:
Variable | Value |
---|---|
AUTH_MODE | oidc |
OIDC_ISSUER | accounts.google.com |
OIDC_JWKURL | https://www.googleapis.com/oauth2/v2/certs |
OIDC_REDIRECT_URI | http://localhost:8080/oidc-login |
OIDC_USER_AUTH_URI | https://accounts.google.com/o/oauth2/auth |
OIDC_ACCESS_TOKEN_URI | https://www.googleapis.com/oauth2/v3/token |
OIDC_CLIENT_SECRET | 24 character string |
OIDC_CLIENT_ID | id_string + .apps.googleusercontent.com |
Information on how to create an OpenId Connect application using Google OAuth 2.0 APIs can be found here.
The following values apply to an Okta provider:
Variable | Value |
---|---|
AUTH_MODE | oidc |
OIDC_ISSUER | https://dev-447786.okta.com/oauth2/default |
OIDC_JWKURL | https://dev-447786.okta.com/oauth2/default/v1/keys |
OIDC_REDIRECT_URI | http://localhost:8080/oidc-login |
OIDC_USER_AUTH_URI | https://dev-447786.okta.com/oauth2/default/v1/authorize |
OIDC_ACCESS_TOKEN_URI | https://dev-447786.okta.com/oauth2/default/v1/token |
OIDC_CLIENT_SECRET | 40 character string |
OIDC_CLIENT_ID | 20 character string |
Information on how to create an OpenId Connect application in Okta can be found here.
OpenID Connect employs OAuth 2.0 access tokens to allow client apps to retrieve user information from the UserInfo endpoint.
Additionally, access tokens can carry claims which are used by the protected resources to determine if the user has access to the resources.
An OpenID provider for Onix Web API will have to add a new access token scope called onix.
The following picture shows how to add the new scope in Okta:
In order to authorise logged on users, the OAuth 2.0 access token must contain a claim called roles under the onix scope, with a comma separated list of the role names granted to the user.
The following picture shows an example of adding a static roles claim in an Okta OAuth 2.0 access token:
Typically, the value of this claim should be dynamically determined based on the user group membership in the backing directory.
The access token should look like this:
{
"ver": 1,
"jti": "AT.0W3...DdQuL-qMGl...wuuK-u6Y...0I",
"iss": "https://dev-447786.okta.com/oauth2/default",
"aud": "api://default",
"iat": 155....369,
"exp": 155....969,
"cid": "0oajnv4.....YRtO0356",
"uid": "00ujnmh.....zmyXY356",
"scp": [
"openid",
"onix"
],
"sub": "[email protected]",
"role": "ADMIN, WRITER"
}
The roles are then mapped within the Onix WAPI to data model logical partitions via Read, Write and/or Delete privileges according to the model described here.