User Identity & Authorization
Overview
STRATO relies on the OAuth 2.0 protocol for authorization.
We also use OpenID Connect as an additional identity layer on top of OAuth 2.0 to provide a flexible end-to-end solution for user identification while preserving high security levels.
Identity Providers
An Identity Provider is simply a service that provides user authorization and authentication based upon some user credentials.
STRATO can integrate with most of the identity providers supporting the OpenID Connect (OIDC) layer, including:
- Microsoft Identity Platform (Azure Active Directory)
- AWS Cognito
- Google Identity Platform
- RedHat Keycloak
- Ping Identity
- Okta
- Auth0
- GitHub
- and others
STRATO authorizes API requests based on the validity of the access token provided in the HTTP header of the call. An access token follows the JSON Web Token (JWT) format. A token is validated and verified by STRATO using the public-keys obtained from OpenID Discovery URL of the identity provider.
A user is uniquely identified in STRATO based on the user information from the token payload.
Access token
Following the OpenID Connect standard, the STRATO API expects an access token in the JSON Web Tokens (JWT) format. The token can be provided with an HTTP call as a Bearer Token in the Authorization header.
Alternatively, STRATO can authorize an API call based on the session cookie provided - this approach is used in STRATO Management Dashboard (A regular Web Application packaged with STRATO). However, this is not a common approach for integrating 3rd party applications with the STRATO API.
A JWT token consists of 3 concatenated Base64Url-encoded strings - Header, Payload, and Signature. Each encoded string is separated by a dot (.
) in the final token:
JWT Access Token example:
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMNkVKeVVTNGlHZGlxUkw3UFpLTGVHNWJyelFOZjM2YkVqbGZIdElPQm5ZIn0.eyJqdGkiOiJlMTEyODQzZS1mZTIxLTRmODktOGViNS03ZDE0YzMxYjc3ZGEiLCJleHAiOjE2MzI3Mzk2NTMsIm5iZiI6MCwiaWF0IjoxNjMyNzM5MzUzLCJpc3MiOiJodHRwczovL2tleWNsb2FrLmJsb2NrYXBwcy5uZXQvYXV0aC9yZWFsbXMvcHVibGljIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVhMGZmODE3LTZiMjYtNDk4Ny1iNjRjLWNmY2UyNTEzOGU3MSIsInR5cCI6IkJlYXJlciIsImF6cCI6InB1YmxpYy1kZXYiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiI5ZGQ0YWE5My02NzAzLTQwMzMtYTI1Zi0zOTVhNzRhM2Q3YzkiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QGV4YW1wbGUuY29tIn0.E-OKSSq6krcI1lrpyb6on7azCveNm6FB4TU0aVYDcqxgTT_0qkcLHTHNmRPFGJiWfSYhT3ghxV1F8SOFSJrkn-5-McC9gUSSmhy3wlr6AjxrGXSBCLRRoO5WfAWTTXis9eJLXwk2F5Lx1FF3GwVpfifUdtMgz61UG3UQcE-91Woo1_ECxEDkkwbcigJ_XR1y_LmoH6VKI6wCRq95qMmas0Szk06XAm-VxYrl_FO5oLQYK2dFYbOpkeKy8XA6-eUn-JJhqtFRqpvyP6_Mw8mnCBRozVJY8QULzavwzlGNnVoDoO7FgfGNFh4oTSWz3hbJOELKvzB4S92n2dyZdkex-w
Tip
You can easily decode a JWT token by pasting it into https://jwt.io. Your token remains secure as the resource does not send the token to the server. Alternatively you can manually decode the token locally by using base64 decoding.
JWT Header
The Header contains the metadata of the token, such as token type and signing algorithm.
JWT Header example:
{
"alg": "HS256",
"typ": "JWT"
}
This data is not directly used by the API and does not require any customization.
JWT Payload
The Payload contains data about the token and the user, including the token issue & expiration timestamps, and information about the user (e.g. username, email address etc.).
JWT Token Payload example:
{
"jti": "e112843e-fe21-4f89-8eb5-7d14c31b77da",
"exp": 1632739653,
"nbf": 0,
"iat": 1632739353,
"iss": "https://keycloak.blockapps.net/auth/realms/public",
"aud": "account",
"sub": "ea0ff817-6b26-4987-b64c-cfce25138e71",
"typ": "Bearer",
"azp": "public-dev",
"auth_time": 0,
"session_state": "9dd4aa93-6703-4033-a25f-395a74a3d7c9",
"acr": "1",
"allowed-origins": [
""
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "email profile",
"email_verified": true,
"preferred_username": "test",
"email": "[email protected]"
}
The payload information is readable by anyone, but its data is protected against tampering since the token is signed by the Identity Provider (see JWT Signature)
JWT Signature
The JWT Signature is hash of the Header, Payload, and identity provider's private-key. This ensures that the information within the JWT is unchanged from when it was created. It can also ensure that the sender of the token is also the agent who rightfully posesses the token.
The signature is used to verify the message wasn't changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is. (jwt.io)
The STRATO API verifies the signature and payload of a token using the public-keys obtained from the Identity Provider configured at STRATO Boot Time. The token expiration timestamp from exp
is also validated.
If the token is valid, STRATO will use the user ID from the payload. The default claim used as a unique user ID is email
but the claim used may be altered by using the OAUTH_JWT_USERNAME_PROPERTY
environment variable at STRATO Boot time (see
STRATO OAuth Configuration)
For more detailed information about JWTs, refer to jwt.io
OpenID Connect Discovery
OpenID Connect (OIDC) provides an easy way to set up an API server and client using the OpenID Connect Discovery protocol.
OIDC-compliant identity providers must have a URL ending with /.well-known/openid-configuration
(also known as "well-known endpoint")
that provides all configuration information publicly. The endpoint returns useful data which is used by the OIDC client for configuration and completing user authorization. The well-known endpoint contains the name of further endpoints from the Identiy Provider from which the client may complete authorization processes such as:
- Fetch a token
- Fetch the public certificate used to verify a token signature
- Logout user sessions
It also contains useful information such as what methods are supported from the Identiy Provider:
- Grant-types (see OAuth 2.0 Grant Types)
- Claims
- Signature Algorithms
Example "well-known" endpoint:
https://keycloak.blockapps.net/auth/realms/public/.well-known/openid-configuration
For a more detailed information about data used in OIDC Discovery, refer to the OIDC official documentation. The OAuth providers like Okta or Auth0 provide good, detailed documentation for OAuth 2.0 and OpenID Connect specifics.
OAuth 2.0 Grant Types
The OAuth 2.0 framework provides multiple ways to obtain tokens from an Identity Provider. Each method is called a "Grant Type".
While some grant-types (like Authorization Code Grant) are client-oriented and multi-step, they require user interaction via a UI and are not intended to be used programmatically, other grant-types (like Client Credentials Grant) are server-side and can be used to obtain a token with a single HTTP call.
Authorization Code Grant
The Authorization Code grant-type is the most common way to obtain access tokens in public environments. It is considered secure and allows control over what sensitive data each party (user, web server, identity provider) gets access to. E.g. the web server does not get the access to user credentials; the user does not get access to OAuth client credentials.
Client Credentials Grant
The Client Credentials grant-type is used by clients to obtain an access token outside the context of a user interacting with an application. In other words, the identity of a token obtained with this grant-type is not linked to a specific user. Instead, it represents the Application (OAuth Client). This method can also be referred to as a "Service User Token". For this type of token, the identity provider includes a generated UUID string or the correct Client ID as the user identification in the token's payload field (e.g. "preferred_username".)
This method is useful for server-to-server API authentication, such as background processes in web applications requiring access to the STRATO API.
Below is an example of an HTTP call made with Curl to obtain a token with the Client Credentials grant-type.
Example
curl -s -X POST 'https://keycloak.blockapps.net/auth/realms/public/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic <base64_credentials_string>' \
--data-urlencode 'grant_type=client_credentials'
base64("client_id:client_secret")
, which would output a string like: bXlfZGVtb19jbGllbnRfaWRfaGVyZTpteV9kZW1vX2NsaWVudF9zZWNyZXRfaGVyZQ==
.
Response
{
"access_token": "<access_token>",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "<refresh_token>",
"token_type": "bearer",
"scope": "email profile",
<...>
}
Resource Owner Password Grant
The Resource Owner Password grant-type requires an Application to handle a user's credentials and therefore should not be used by third-party clients due to the security risks.
It is strongly recommended to use the Authorization Code Grant over this type of grant wherever possible. This is because the Authorization Code grant-type purely relies on the Identity Provider to sign-in the user and eliminates the possibility for an application to collect or mishandle a user's credentials.
However, this grant-type may be very helpful for debugging and testing developing applications, when you need an easy, programmatic way to obtain the tokens of multiple users.
Technically, this grant-type is very similar to Client Credentials Grant with the difference that the application needs to provide the user's username and password in the HTTP request.
An example of a HTTP call made with Curl to obtain the tokens with the Resource Owner Password grant:
Example
curl -s -X POST 'https://keycloak.blockapps.net/auth/realms/public/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic <base64_credentials_string>' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=demouser' \
--data-urlencode 'password=demopassword'
{
"access_token": "<access_token>",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "<refresh_token>",
"token_type": "bearer",
"scope": "email profile",
<...>
}
Refresh Token Grant
This grant-type lets server exchange a refresh token for an access token. A refresh token is a token similar to an access token and is provided in the response with an access token. Refresh tokens usually have the significantly longer lifetimes than access tokens but do not actually grant authorization to an entity. Once an access token expires, an application can "refresh" the token without the user being involved in the process. This can be useful to avoid asking the user to enter their username/password on a Login screen again and reinitializing the Authorization Code grant flow.
An example of HTTP call made with Curl to obtain a token with the Refresh Token grant:
Example
curl -s -X POST 'https://keycloak.blockapps.net/auth/realms/public/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic <base64_credentials_string>' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=<refresh_token_here>'
Response
{
"access_token": "<access_token>",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "<refresh_token>",
"token_type": "bearer",
"scope": "email profile",
<...>
}
More About Grant Types
For more grant-types and more detailed information about the grant-types listed above, refer to Obtaining Authorization section of The OAuth 2.0 Authorization Framework Standard.
Scopes
Scope is a mechanism in OAuth 2.0 to limit an application's access to a user's account. An application can request one or more scopes, this information is then presented to the user in the consent screen, and the access token issued to the application will be limited to the scopes granted. (oauth.net)
The list of requested scopes can be altered in STRATO and your application. For example, in order to not request the user's consent
to share their email address with an application, you may alter the default scope openid email profile
to not include the email
.
In this case you will need to rely on an alternative JWT payload property since the email
property will not be included in the token.
For configuration of your OAuth scope and JWT property for unique user ID, refer to STRATO OAuth Configuration.
STRATO OAuth Configuration
The Identity Provider (OAuth Provider) settings must be provided to STRATO at boot time. STRATO requires that you provide an OAuth Identity Provider since the platform will not handle user authorization itself.
OAuth Environment variables available for configuration:
OAUTH_DISCOVERY_URL
- The string with url of OpenID Connect discovery URL (
.../.well-known/openid-configuration
)
- The string with url of OpenID Connect discovery URL (
OAUTH_CLIENT_ID
- The Client ID of the OAuth2 client configured with the Redirect URI for your STRATO node (e.g.
https://<NODE_HOST>:8080>/auth/openidc/return
). - The Client ID is used in the STRATO Management Dashboard (SMD) web UI for user authentication. It is not required by OAuth2 framework to validate or verify the access tokens in API calls.
- Note: Some OAuth 2.0 Providers support wildcards in Client Redirect URIs (e.g.
https://<NODE_HOST>:8080/*
)
- The Client ID of the OAuth2 client configured with the Redirect URI for your STRATO node (e.g.
OAUTH_CLIENT_SECRET
- Client Secret for the Client ID provided.
OAUTH_JWT_USERNAME_PROPERTY
(default: 'email')- The property name of JWT token payload to be used as the user's unique identifier in STRATO.
OAUTH_SCOPE
(default: 'openid email profile')- The OAuth 2.0 scopes used in 'session' cookie verification (only alter this value if you are also using a custom
OAUTH_JWT_USERNAME_PROPERTY
that is not inlcuded in the default scope. Refer to your OAuth 2.0 Identity provider's documentation for more information) - Note: Some OAuth 2.0 Providers (like Azure AD) require a custom scope for OpenID Connect to work - check the Known Identity Provider Configuration Specifics section for more details.
- The OAuth 2.0 scopes used in 'session' cookie verification (only alter this value if you are also using a custom
Warning
Changing the Identity Provider on an existing node will cause serious problems. It will prevent the users from accessing their existing STRATO accounts and prevent the node accessing it's own private key. For more information about STRATO user accounts refer to Vault section.
An example command for STRATO on-premise deployment (localhost, no SSL, connected to BlockApps' DevNet):
Example
NODE_HOST=localhost:8080 \
HTTP_PORT=8080 \
OAUTH_ENABLED=true \
OAUTH_DISCOVERY_URL=https://<identity_provider_host>/<...>/.well-known/openid-configuration \
OAUTH_CLIENT_ID=<client_id> \
OAUTH_CLIENT_SECRET=<client_secret> \
OAUTH_JWT_USERNAME_PROPERTY=preferred_username \
VAULT_URL=https://<vault_hostname>:<vault_port> \
Relation of OAuth Users and STRATO Accounts
STRATO identifies the user who is calling the API based on the two parts of the payload JWT token provided in the request: the sub
(by default) property value (the user's unique identifier) and the iss
field (known issuer claim, to identify username associated with oauth provider.)
Having the access token obtained from the Identity Provider does not mean you can immediately make blockchain transactions on STRATO.
In order to start making transactions in STRATO, a user first needs to create an account on the STRATO blockchain.
This account is represented by a public-private key pair and blockchain address stored in the STRATO Vault.
STRATO account creation is done with an API call to POST /strato/v2.3/key
with the access token provided in Authorization header of the request.
Please refer to API Introduction - Create an Account or the Complete API Reference for examples
and more information about creating an account with the STRATO API.
STRATO Vault Basics
STRATO Nodes connect to an external STRATO Vault to securely store STRATO account data. You can think of it as a mapping from an OAuth user to a STRATO account. Since a STRATO vault is external from the actual STRATO node, multiple nodes may to connect to the same STRATO vault at once, so users can access their key information from any STRATO node on a network using that STRATO vault where their key is stored. A STRATO Vault is connected to an OAuth provider to manage user authorization and identities. The OAuth provider is controlled by the manager of the STRATO Vault. For more information see : STRATO Vault Key Storage
For each STRATO account created, STRATO Vault keeps the following user data in its database:
- Unique Identifier (matches the user unique identifier from the payload of JWT token)
- Encrypted private-key
- Encrypted public-key
- Encrypted blockchain address
- Automatically generated random salt string
On every incoming API request making a transaction, STRATO retrieves the private-key of the user associated with the unique identifier in the access token from the Vault to sign the transaction.
Encryption
All sensitive data stored in the key vault is encrypted using the hashing algorithm and the salt strings.
The STRATO implementation uses the unique random salt strings generated automatically for each of the users and stored along with their information in the database, and the STRATO Vault global password, which is set by a node administrator at STRATO boot time. The STRATO Vault global password is only kept in memory.
Note
For more information about STRATO boot configuration options, see Network Setup.
A node administrator can set the vault password at boot time by using the PASSWORD
environment variable:
PASSWORD=my_super_secret_password
A STRATO node cannot be started if the incorrect password is provided.
Note
STRATO Vault global password needs to be re-entered by the node administrator on every system or Docker container restart.
Warning
STRATO Vault does not protect the user's data from anyone who has the direct (SSH) access to the machine STRATO is running on. A person with this type of access can enter into the Vault's database and replace the user's data stored there. Furthermore, they may extract the user's keys from the Vault database if they know the Vault global password.
Tip
STRATO can be used without the Vault to sign transactions. Instead STRATO allows users to manually prepare and sign their transactions with their own credentials. See more in the Manual Identity Management Documentation.
Integrate an Application with STRATO API using OAuth
When developing a web application, It is the application's responsibility to implement user authorization logic (using one of the OAuth 2.0 Grant Types).
All tokens generated under the same Identity Provider's user directory (can also be referred to as 'Tenant', 'Realm' or 'Directory' by different Identity Providers) can be accepted and verified by the STRATO API. Since one user directory is represented by one OpenID Connect Discovery URL, in order for STRATO to authorize API calls from an application, then all three, the application, Vault and STRATO must be set up with the same OpenID Connect Discovery URL.
End-to-end authentication sequence diagram
Known Identity Provider Configuration Specifics
For steps to configure the selective Identity Providers to comply with STRATO, please refer to Identity Provider Configuration Steps
AWS Cognito
AWS Cognito does not support multiple Grant Types on single OAuth2 Client. Use separate OAuth2 clients for each grant-type in your Application.
For example, you will need two OAuth2 clients to implement the Authorization Code and Client Credentials Grant types for authentication in single Application. This can be implemented in in a custom way in your Application code.
But when using BlockApps Rest SDK blockapps-rest the config.yaml can be as follows:
<...>
nodes:
- id: 0
url: "https://<strato_node_host>"
publicKey: "6d8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"
port: 30303
oauth:
appTokenCookieName: "myapp_session"
appTokenCookieMaxAge: 7776000000
openIdDiscoveryUrl: >-
https://cognito-idp.<amazon-region-id>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration
scope: openid email profile
clientId: <client id of AppClient myapp-localhost>
clientSecret: <client secret of AppClient myapp-localhost>
redirectUri: https://<application_host>/api/v1/authentication/callback
logoutRedirectUri: https://<application_host>/
# oauthUsername: username/email/sub #(check for what's in token payload, if needed)
serviceFlow: client-credential
serviceFlowClientId: <client id of AppClient myapp-localhost-service>
serviceFlowClientSecret: <client secret of AppClient myapp-localhost-service>
# serviceFlowClientScope: openid email profile #(TBD if required)
<...>
Microsoft Identity Platform (Azure Active Directory)
- Microsoft Azure Active Directory may require the custom scope for your Application:
'openid offline_access <client_id>/.default'
offline_access
- to provide the refresh token along with access/id tokens
<client_id>/.default
- to enable the client credentials flow (see Permissions and consent in the Microsoft Identity Platform for more details ){target=_blank}