Usage
In this section, we show you how to use the authentication-service
.
Login Flows
The service handles the authorization_code
and the legacy password
OAuth2 grant types.
Authorize
API Signature: GET /authorize
The /authorize
endpoint is useful for the login flow with grant type authorization_code
.
The authorization endpoint accepts the following query parameters:
- appId: the
APP_ID
key from the configurations file - providerId: the
PROVIDER_ID
key from the configuration file, under the correspondingAPP_ID
An example curl:
curl --request GET 'https://app.example.com/authorize?appId={{APP_ID}}&providerId={{PROVIDER_ID}}'
When called, the authentication service redirects to the IdP's authUrl endpoint, which in turn is expected to redirect to the redirectUrl specified in the configurations file with the query string parameters code and state.
Example of redirect URL:
https://app.example.com/callback?code={{CODE}}&state={{STATE}}
Your application should then implement the logic to call the POST /oauth/token with authorization_code grant type with the information passed in the above query string, as per the Authorization Code Grant OAuth2 flow.
In case you are using a web app this request can be made directly from the browser; in this case the user will be redirected to an external login page, and then, back to the custom callback URI when the login succeeds.
Redirect parameter
You can pass a redirect url or path to the request, by means of the redirect
query string parameter. After a successful login, the /oauth/token
endpoint will set the Location
header to the specified url.
The value can either be an absolute url, or a path relative to the root of your web application.
If a request comes with no redirect
query parameter, the Location
header will be set to the defaultRedirectUrlOnSuccessfulLogin
app setting parameter, if specified.
In all other cases the Location
header will not be set.
The response status code of a successful request is always 200 OK
, which means that browsers will ignore by default the Location
header. It is responsibility of the client implementation to use the redirect url when needed.
It is strongly recommended to set the allowedRedirectUrlsOnSuccessfulLogin
parameter (available from v2.8.0) on the app configuration, in order to avoid the Open Redirector vulnerability.
If you set this parameter, the service will refuse to accept any redirect url not present in the set. This prevents an attacker client to set malicious redirect URLs. Any request providing a redirect URL not included in the set will result in a 400 Bad Request
, and the token will not be issued.
This parameter may become required in the next releases of the service.
Client generated state
This feature is turned off by default for retrocompatibility reasons, but its usage is strongly encouraged.
To enable this feature, you need to set the config parameter variable authorizeStateRequired
to true
, in each app where you need it.
To achieve a better level of security, the client should take charge of the process of the state generation. This enables protection against CSRF attacks, as documented here.
The client must follow these steps:
Generate a random state string. The randomly generated string should be sufficiently hard to guess, as described here. For instance, an UUID v4 is compliant with this specification.
Save the state on the client application side (for example in local storage), making sure the location where it is saved it is only accessible by the client.
Call the
/authorize
endpoint, passing the state in the query string, like in the following example:curl --request GET 'https://app.example.com/authorize?appId={{APP_ID}}&providerId={{PROVIDER_ID}}&state=<some-hard-to-guess-string>'
Once the redirect URL is called, the client must check that the state in the redirect URL querystring matches the one it generated at the first step. If it doesn't, the client must refuse to proceed in the token request.
If the state validation check succeeds, the client can now obtain a token by calling the
/oauth/token
endpoint, as described in the related section.
Get JWT Token
API Signature: POST /oauth/token
This endpoint is used to obtain the token, that the user will then use to authenticate to the endpoints.
Authorization code grant type
The body of the request, in JSON format, is composed of the following fields:
{
"code": "{{CODE}}",
"state": "{{STATE}}"
}
These fields are usually retrieved from the redirect url query string.
Password grant type (DEPRECATED)
The password
grant type usage is discouraged and with limited support by providers.
Please only use this only if authorization_code
grant type is not applicable for your use case.
Check your provider documentation to make sure it is supported.
The body of the request, in JSON format, is composed of the following fields:
{
"grant_type": "password",
"username": "{{USERNAME}}",
"password": "{{PASSWORD}}",
"appId": "{{APP_ID}}",
"providerId": "{{PROVIDER_ID}}"
}
Regardless of the used grant_type
, the endpoint will return a JSON object with accessToken, refreshToken and expiresAt.
{
"accessToken": "my-access-token",
"refreshToken": "my-refresh-token",
"expireAt": 1234567890
}
Web site cookie configuration
If you are using a website client, you can set the isWebsiteApp
field to true
in the service configuration; this way, the token API will return two session cookies:
- a cookie named sid, that contains the access token as value
- a cookie named refresh_token, that contains the refresh token as value
The cookies have the following default configuration:
- HttpOnly:
true
- Secure:
true
- Path:
/
- SameSite:
Lax
You can further customize the cookie response by adding the sidCookieCustomAttributes
and/or the refreshCookieCustomAttributes
to the service configuration, at application level.
In particular, you can change the SameSite
attribute to Strict
, and set the Domain
and the Path
attributes to a value of your choice.
Here is an example snippet of the configuration:
{
...
"isWebsiteApp": true,
"sidCookieCustomAttributes": {
"sameSite": "Strict",
"domain": "example.com"
},
"refreshCookieCustomAttributes": {
"sameSite": "Strict",
"domain": "example.com",
"path": "/auth"
}
...
}
Refer to this documentation for a thorough explanation of the mentioned cookie attributes.
For security reasons, you cannot change the other attributes of the cookie, and you cannot set the SameSite
attribute to None
.
The JWT Token
The authentication service generates a JWT Token compliant with the RFC 7519 specification.
In particular, the service populates the standard claims:
exp
with the expiration date as unix timestampiat
with the issuance date as unix timestampsub
with theuserId
, which is the Mongo Id of the user CRUD documentjti
with a random generated UUIDv4iss
with theissuer
specified in the current application config
Along with the above mentioned standard claims, the token contains a custom claim named user
, which is an object with the following structure:
{
"properties": {
"userId": {
"type": "string"
},
"groups": {
"type": "array",
"items": { "type": "string" }
},
"email": {
"type": "string"
},
"name": {
"type": "string"
},
"userSettingsURL": {
"type": "string"
},
"providerUserId": {
"type": "string"
},
"metadata": {
"type": "object",
"additionalProperties": true
},
"permissions": {
"type": "array",
"items": { "type": "string" }
}
}
}
The data are populated from the user CRUD collection. The providerUserId
and metadata
fields are included and populated only if configured, as explained here.
Furthermore, permissions
field is set only in case any permission is added to the permissions array on the document containing user metadata within the User collection.
Refresh token
API Signature: POST /refreshtoken
The endpoint POST /refreshtoken
allows to issue a new token, by using the refreshToken
received in the Token request.
The endpoint needs to be called with a POST, like in the following example:
curl --location --request POST 'https://app.example.com/refreshtoken' \
--header 'Content-Type: application/json' \
--data-raw '{
"refreshToken": {{refreshToken}}
}'
Under the hood, the authentication service will call the provider refresh token endpoint, and then it will refresh its token.
The response is the same as the /oauth/token
:
{
"accessToken": "my-access-token",
"refreshToken": "my-refresh-token",
"expireAt": 1234567890
}
In case the isWebsiteApp
application flag is true
, the /refreshtoken
also sets the sid and refresh_token cookies with the new values.
Delete session cookies on refresh token failure
This feature is disabled by default, you can enable it by setting the env variable INVALID_REFRESH_TOKEN_WIPES_COOKIES
to true
.
When enabled, if the refresh token found in the refresh_token
cookie is empty or not valid, the endpoint wipes both the session cookies, effectively invalidating the user session.
Your web application should logout the user in this situation.
For technical limitations, setting custom Path
or Domain
attributes on the cookies via the sidCookieCustomAttributes
and/or refreshTokenCookieCustomAttributes
settings will prevent the cookies to be wiped.
Skip provider refresh token
If in your provider you set the skipProviderRefreshToken
option, the authentication service will validate the provider refresh token by calling the provider userinfo before issuing the refresh token request.
The refresh token on the provider will only be issued if the validation fails.
Logout
API Signature: GET /logout
This API takes charge of logging out the user, clearing the access token and the refresh token on both server side and, in case of webapps, on the frontend side.
On server side, the tokens are deleted from Redis.
The request should contain the access token in the Authorization
header and/or the sid and refresh_token cookies.
It also accepts a request with only the refresh_token set: in this case it only deletes the session associated to the given refresh token from Redis, since the access token is already expired.
When isWebsiteApp
flag is set to true, both the sid and refresh_token cookies are unset, by means of a Set-Cookie
response header with expiration dates set in the past.
Redirect parameter
Depending on whether you added the logoutUrl
parameter in the service configuration, at provider level, the /logout
endpoint will behave differently, as explained below.
No logoutUrl
parameter
If you did not add the logoutUrl
parameter to the provider configuration, the endpoint will redirect
the user to the specified redirect
query parameter. You can configure the frontend of your web
application to redirect to the /logout
endpoint of the provider.
As a result, the user will be signed out from both the provider and the application.
You can specify an absolute url, or a path relative to the root of your application.
With logoutUrl
parameter
This behavior works only with the OIDC compliant providers. Please refer to your specific provider documentation to check if it is supported.
The following providers have no support for this feature:
- Bitbucket Server
- Github
- Gitlab
The logoutUrl
parameter will be ignored in case of non-OIDC compliant providers, and a warning log will be raised.
For OIDC compliant providers, you may need to add the openid
scope in the provider configuration.
If you use a generic provider you have the responsibility to check for the OIDC compliance, and in case it is not compliant, the endpoint will likely not work as expected.
When called with the logoutUrl
parameter set in the service configuration, the endpoint will append to it a query string, with the following parameters:
post_logout_redirect_uri
: it is set to theredirect
query parameter, if present. Otherwise, it is not set.id_token_hint
: it is set to theid_token
, retrieved from the provider during login, and stored in the backend session along with the access token.
The endpoint will then redirect the user to the logoutUrl
with the appended query string, causing the user to be signed out from both the provider and the application.
Please note that in this case the redirect
parameter must be an absolute url, pointing to a proper location of your web app. Relative paths are not accepted: the authentication provider will refuse them.
Please also make sure you correctly configure your provider adding the redirect urls you wish to use to the allowed post_logout_redirect_uri
s.
User Info
API Signature: GET /userinfo
The /userinfo
endpoint returns the contents of the user
claim of the JWT Token.
See the related section for more information on how the JWT Token is built and how you can personalize it to achieve a customized userinfo response.
You can contact the endpoint in this way:
curl --location --request GET 'https://app.example.com/userinfo' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <access_token>'
Custom claims
If you need to have additional custom claims in the user
claim of the token and, as a consequence, in the userinfo response, you have to include the customTokenClaims
object in the app configuration.
The object contains the following fields:
includeProviderUserId
, if set totrue
theproviderUserId
will be included in theuser
claim, and populated with the user id of the provider. The default behavior is not to include the field.metadataFieldsToInclude
, an array of strings. If it contains at least one element, ametadata
object is added to theuser
claim and populated with the specified fields taken from the correspondingmetadata
object of the CRUD user collection.
For example, let's consider the following document as a record of the user collection:
{
"_id": "some-mongo-id",
"name": "John Doe",
"groups": ["someGroup"],
"username": "someUsername",
"email": "johndoe@example.com",
"providerId": "someProviderId",
"providerUserId": "some-id",
"metadata": {
"firstName": "John",
"surname": "Doe",
"address": {
"city": "Milan",
"country": "Italy"
}
},
"permissions": [
"read:resource",
"update:resource"
]
}
and have the authentication service configured as follows:
{
"apps": {
"APP_ID": {
"providers": {
"PROVIDER_ID": {
...
}
},
...
"customTokenClaims": {
"includeProviderUserId": true,
"metadataFieldsToInclude": [
"surname",
"address"
]
}
}
}
}
Then, the JWT generated by the authentication-service
upon successfully completing the authorization flow would look like the one below:
{
"user": {
"userId": "some-mongo-id",
"groups": [
"someGroup"
],
"email": "johndoe@example.com",
"name": "John Doe",
"providerUserId": "some-id",
"metadata": {
"surname": "Doe",
"address": {
"city": "Milan"
}
},
"permissions": [
"read:resource",
"update:resource"
]
},
"exp": 123456789,
"jti": "some-jti",
"iat": 123456789,
"iss": "some-iss",
"sub": "some-mongo-id"
}
Integration with the Authorization Service
The /userinfo
endpoint can be used by the Authorization Service to determine whether the requested resource can be accessed by the current user, based on the contents of the groups
array.
You need to set the following variables in the configuration with these values:
- USERINFO_URL=
http://authentication-service/userinfo
(NB: change the hostname if it has been named differently) - CUSTOM_USER_ID_KEY=
userId
- HEADERS_TO_PROXY=
x-request-id,request-id,cookie,authorization,client-type,host,x-forwarded-host
Custom properties to proxy
If you have configured some custom claims in the authentication service, you can also add them to the authorization service Custom properties to proxy.
Just add the name of the fields as a comma-separated list to the following authorization service environment variable:
USER_PROPERTIES_TO_PROXY=metadata,email,name,providerUserId,permissions
This way, a JSON string will be set to an header with name miauserproperties
, populated with the metadata field of the userinfo response.
Referring to the example above, the header will have the following contents:
{
"email": "johndoe@example.com",
"name": "John Doe",
"providerUserId": "some-id",
"metadata": {
"surname": "Doe",
"address": {
"city": "Milan"
}
},
"permissions": [
"read:resource",
"update:resource"
]
}
Token Info
API Signature: GET /tokeninfo
This endpoint returns potentially confidential information, that a user may not be supposed to have; therefore, depending on your specific use case, you probably don't want to expose it publicly.
This endpoint has the same response of the userinfo, with in addition a provider
field, populated with the following fields:
- accessToken: the access token obtained from the IdP.
- id: the provider id of the currently used configuration
- baseUrl: the OAuth2 base url of the provider, as set in the configuration
- userId: the provider id of the current user
An example of response:
{
"userId": "63cacfa530a2a89a7f057dce",
"groups": [
"testgroup"
],
"email": "some@email.com",
"name": "John Doe",
"provider": {
"accessToken": "provider-token",
"id": "provider-id",
"baseUrl": "provider base url",
"userId": "provider-user-id"
}
}
You can use the /tokeninfo
endpoint as USERINFO_URL
in the authorization service. This is useful if you want the information included in the provider
key to be added to the miauserproperties
header.
Revoke sessions
This API is intended to be used by a privileged user (e.g. an administrator or a customer service operator) or application. Make sure to expose it with a proper authorization config.
API Signature: DELETE /sessions
This is an admin feature that revokes all sessions of the specified user. This is useful when the user is being blocked or deleted on the provider side.
You can contact the endpoint this way:
curl --location --request DELETE 'https://app.example.com/sessions/:userId' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <access_token>'
The endpoint in case of success will reply with status code 200 and the number of deleted elements. An example of response:
{
"count": 2
}
Cleanup all users queue
This API is intended to be used by a privileged user (e.g. an administrator or a customer service operator) or application (e.g. a cronjob). Make sure to expose it with a proper authorization config.
API Signature: DELETE-/expired-sessions
This API is helpful to clean up all the expired sessions of all the users.
This is especially useful when the STORED_ACCESS_TOKEN_NUMBER environment variable is a big number, in order to keep stored data in Redis to the minimum.
It's recommended to setup a CronJob that periodically invokes this API; the period can be tuned with respect to the queue length and to other parameters that depend on your use case, such as the number of logins per unit of time.
The operation is deliberately non-atomic, as it triggers a potentially long and CPU-intensive task that could block Redis.
In case of success, the endpoint will return a 204 No Content response.
Cleanup a specific users queue
The endpoint can receive also as a query params a specific userID. In this case, only the session of the specified user are cleaned up.
Here is an example:
curl --location --request DELETE 'https://app.example.com/expired-sessions/:userId`' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <access_token>'
Webhooks
When a new User is activated on the ID Provider, the UserInfo collection can be synchronized through this webhook. (Currently only Okta provider is supported)
Pay careful attention when configuring the webhook as the endpoint must be properly protected as it may allow anyone to create new user records.
The webhook is made of 2 endpoints:
- GET
/webhooks/apps/:appId/providers/:providerId/user
used to validate the hook by the OID Provider - POST
/webhooks/apps/:appId/providers/:providerId/user
used to handle the user activation
Validate the Webhook (Okta)
In order to validate the webhook, Okta sends a GET request on the webhook url with a specific header x-okta-verification-challenge
and expects to receive in response the same value of the header.
Security Configuration
In order to secure these endpoints from unwanted requests (that will end up in users being added to the CRUD collection), you need to secure the Endpoint (for instance using an API Key).
Remember to keep the API Key secure and to share it only with who is going to consume the webhook!
JWT Token signing algorithms
Since v3.6.0 the service provides two methods for the token signing.
Symmetric signing algorithm HS256 (default)
The symmetric signing algorithm is used by default for retrocompatibility reasons. For new service setups, the usage of an asymmetric algorithm is strongly encouraged.
By default, the service uses the symmetric signing algorithm HS256
to sign the token. The signing key is provided by means of the MIA_JWT_TOKEN_SIGN_KEY
env variable.
It is suggested to generate and use an HMAC of least of 512 bytes, for example with the following command:
openssl rand -base64 512
Asymmetric signing algorithm RSA256
The service supports the asymmetric signing algorithm RSA256.
Firstly you need to set the MIA_JWT_TOKEN_SIGNING_METHOD
env to RS256
.
Then you need to setup the RSA key pair as explained in the following section.
RSA key setup
An RSA key is a type of asymmetric cryptographic key used in public-key cryptography. In an RSA key pair, there are two keys: a public key and a private key.
The public key can be freely distributed and is used to encrypt messages that can only be decrypted with the private key. The private key, on the other hand, is kept secret and is used to decrypt the messages that have been encrypted with the public key.
Creation
This service accepts a private RSA key to sign the generated JWT (JSON Web Token). NIST recommends at least 2048-bit keys for RSA. An RSA key length of at least 3072 bits should be used if security is required beyond 2030. In this guide, we will use a key of 4096 bits.
To generate a new private key, you could run:
openssl genrsa -out ./private.key 4096
The service also supports password protected private keys. To encrypt a private key, firstly generate a random string to be used a password, for example with this command:
openssl rand -base64 256
You can now protect the private.key
with the password using the following command:
openssl rsa -aes128 -in privkey.pem -out privkey.pem -traditional
Enter the password when required.
The private key is encrypted and stored in the same private.key
file, which is overwritten.
Set the generated password to the env MIA_JWT_TOKEN_PRIVATE_KEY_PASSWORD
to allow the service to decrypt and use the private key.
Deploy with mlp
If you use mlp, the Mia-Platform deploy CLI tool, to release custom secrets add these lines to the mlp.yaml
file in your project:
secrets:
- name: "authentication-service-private-key"
when: "always"
data:
- from: "literal"
key: "private.key"
value: "{{PRIVATE_KEY}}"
The name
is at your discretion; make sure to avoid collisions.
The PRIVATE_KEY
secret variable must be populated with the ./private.key
file contents.
Deploy the secret with Kubernetes
You should set the key as volume from secret in the authentication-service
service, or in the namespace where the service is deployed.
Refer to the related Kubernetes doc
Service configuration
To use the private key you generated early, you have to follow these steps:
- set the
MIA_JWT_TOKEN_SIGNING_METHOD
toRS256
- set the
MIA_JWT_TOKEN_PRIVATE_KEY_FILE_PATH
to the file name of the mounted private key. - choose an arbitrary
kid
to the private key (e.g. an UUIDv4), and set it to theMIA_JWT_TOKEN_PRIVATE_KEY_KID
. It will be used to generate a public key, and also included askid
claim in JWT tokens signed with this key.
Public Key Endpoint
Endpoint Signature: GET /.well-known/jwks.json
As per the OpenID Connect specs, the Authentication service exposes this endpoint to obtain an object that contains an array of valid JWK values in its keys
field.
Those JWKs could be used to verify the signature of the JWT that is presented by a client to another service.
At the moment, you can configure only one Private Key. For this reason, the endpoint will always return one public key, calculated from the given private key.
Example cURL request:
curl 'https://example.org/authn/.well-known/jwks.json'
Example response:
{
"keys": [
{
"kid": "keyA",
"use": "sig",
"kty": "RSA",
"alg": "RSA256",
"n": "sR6WjRHDNXgzBTgYr-ayhSlxdt65FIrhTytZN9dZczDC8Uqt6Cynstq3eoAfLcrxKAyj4X3J4TRxSEOL78WUisLAADHU6oEsqeuB97kVN4PcPnd63H3naOiLioc2-9L1TtUMVB4H6G5ZkKQAgrwjpHSztJF0iYaXOQhEcBlCynltuEVuyK96tvnDVqXCfhsSFweP7KorcfMj4YYj5OT2ADlAFzBQ2qppd9BpJidHGD6auCsI7vjmNCEq49v9UOiQs2XbjN-ddr9nvNBBK5bVtjGkfUPNt6uAV1AWMboVjobcAnDH2AD8W--3JUl1ffguC_fsHpPjrNoH0hCbPFfEb2YK2DX1vKhYKX3u199gc4B1q0l1JTs8AJcFbf7d63FKa6O-5V97fLK9lJYd8adF8NZiJlXjFCR-LmAYmjxmsBmByImEenEzDxuuubitSWFt47L9eGV9eY7zmnD0FV_jbwXYCcod4R46vnjabzpUcnd3VqiruUwnquHNGgj2yJpT7CMCHpK9dVlMUY8cWIfYXn4si_RrRp_E2EIkWKkSyplBWMjIK_KhjuSi_YOYNSg3OKXOGmYMcCxXUnwPIIW5n-MdbO6WC8bqhpLU1_XisfaL-V8jEOjAs0dQ9dQyvvP9ckrC753FGARXtdqwnyb2d3r3r3cLh-eQo05TyLqHoEk",
"e": "AQAB"
}
]
}
Service Metrics
If you set the EXPOSE_METRICS
environment variable to true
, the service will expose the /-/metrics
endpoint, which returns the following metrics:
Metric Name | Description |
---|---|
http_request_duration_seconds | The duration of the HTTP requests, in seconds |
It is possible to customize the metrics prefix by setting the NAMESPACED_METRICS_PREFIX
environment variable. The default value is authentication_service
.
Get Provider idToken
This feature is only available for the provider microsoft
and disabled by default.
Only activate it if needed for your specific use case.
API Signature: GET /provider/idtoken
The endpoint is exposed only if the apps.<appId>.providers.<providerId>.exposeProviderIdToken
config parameter is set to true
.
When contacted with a valid access token, either in the sid
cookie or in the Authorization
header, it returns the provider OIDC id_token
relative to the current session, that was retrieved from the provider during the login or the token refresh.
The response is a JSON string reflecting the following example:
{
"idToken": <the-provider-id-token>
}