Skip to content

Martini Services Authentication & Authorization

Users and Groups

Not all APIs are designed for public access; many require specific privileges for access. In the Martini platform, configuring users and groups is a fundamental aspect of implementing robust security measures. This configuration allows for the granular control of permissions, ensuring that different components of your application are securely accessed only by authorized users.

Understanding Martini's Security Model

Martini's security model is built around two core concepts: Users and Groups. These concepts play a crucial role in the platform's approach to securing application components and managing access control.

Users

A user in Martini is defined by at least a username and a password. This basic identification mechanism forms the credentials used when authenticating requests to the Martini platform. Users are the primary entities that interact with the platform, each having specific permissions and access rights.

Groups

Groups in Martini offer a streamlined method to manage authorizations for users with similar access needs. By grouping users, you can assign permissions to multiple users at once, simplifying the management of access rights and reducing the overhead of individual user configuration.

Managing Users and Groups

Efficient management of users and groups is crucial for maintaining the security and operational integrity of your applications within Martini. This section outlines the procedures for adding, removing, and managing users and groups both through the Martini interface and programmatically via REST API.

Accessing the Users and Groups Dialog

To manage users and groups within the Martini platform, you need to access the Users and Groups dialog. This interface provides a comprehensive view of all users and groups configured in your Martini instance, allowing for easy management tasks such as adding, editing, or removing users and groups. To access this dialog, follow these steps:

  1. In the Navigator view, right-click on your Martini instance.
  2. From the context menu that appears, select "Users and Groups."

This action will open the Users and Groups dialog, where you can perform various management tasks.

Managing Users and Groups via REST API

Martini extends its management capabilities by providing REST endpoints dedicated to the maintenance of users and groups. This feature is particularly useful for automating user and group management tasks, allowing for remote operations without the need to interact directly with the Martini interface. The REST API supports several operations, including:

  • Searching for Users and Groups: Allows you to query the list of existing users and groups based on specific criteria, facilitating easy lookup and management.
  • Adding or Removing Users and Groups: Provides the ability to programmatically add new users and groups to your Martini instance or remove existing ones, offering flexibility in managing access and permissions.
  • Adding or Removing Users to and from Groups: Enables the association or dissociation of users with groups, allowing for dynamic permission management and access control.
  • Refreshing User OAuth Tokens: Supports the operation to refresh OAuth tokens for users, ensuring continuous secure access for applications and services using OAuth for authentication.

Adding a User

Adding users to your Martini instance is a straightforward process that allows you to define who can access your application and with what privileges. Follow these steps to add a user:

  1. Initiate User Creation: Click the green '+' button to start the process of adding a new user.
  2. Fill in User Details: A form will appear requiring you to input the following details for the new user:

    • Username: Must be unique. Martini Desktop automatically suggests a generic username, which you can accept or change to suit your needs.
    • Password: Should be between 5 to 20 characters in length to ensure security.
    • Display Name: This is the name that will be displayed within the Martini interface, requiring 4 to 20 characters.
    • Email Address: Must be a valid email format, as this may be used for notifications or password resets.
  3. Save the User: After filling in the required details, click "Save" or "OK" to finalize the creation of the new user. The user will now be added to your Martini instance and can access the application based on their assigned permissions.

Removing a User

There may be occasions when you need to remove a user from your Martini instance. To do this, follow these steps:

  1. Select the User: Identify and select the user you wish to remove. If you cannot find the user immediately in the list, use the search functionality by entering a portion of the user's username, email address, or display name. This will filter the list and help you locate the user quickly.
  2. Initiate User Removal: Click the red 'x' button associated with the user you wish to delete. This action will prompt a confirmation dialog.
  3. Confirm Removal: Confirm your intention to remove the user. Once confirmed, the user will be permanently deleted from your Martini instance.

Editing User Details

Maintaining up-to-date user information is essential for effective user management within Martini. This process includes updating user credentials, display names, group memberships, and other relevant details. Here's how you can edit user details:

  1. Select the User: Begin by selecting the user whose details you wish to edit. If you're unable to locate the user directly in the list, utilize the search functionality by entering parts of the user's username, email address, or display name. This will filter the list and help you find the user more easily.

  2. Access the Edit Form: Once you've selected the user, an edit form will appear, displaying the current details of the user. Here, you can modify various properties such as:

    • Username: Although typically not editable after creation, some systems may allow changes.
    • Password: Update the user's password. Ensure it adheres to your platform's security requirements.
    • Display Name: Change the name that appears within the application or communications.
    • Email Address: Update the email address if it has changed, ensuring it remains a valid and active address for notifications or password resets.
    • Group Memberships: Add or remove the user from groups to adjust their permissions and access rights. If you're adding a user to a group and cannot find the group, use the search functionality by entering parts of the group name.
  3. Save Changes: After making the necessary adjustments, click "Save," "OK," or "Update" to apply the changes. The user's details will be immediately updated in the system, reflecting the new information or group memberships.

Creating and Getting the Access Token of a User

Access tokens play a crucial role in securing APIs by ensuring that only authorized users can access them. In the context of Martini, generating and retrieving access tokens for users allows you to manage secure access to APIs that utilize OAuth2 for authentication. Here's how you can create or get an access token for a user:

  1. Select the User: Begin by selecting the user for whom you want to generate or retrieve an access token. This user must have the appropriate permissions to access the APIs you wish to secure.

  2. Enable the User: Ensure that the user's account is enabled. If the user's account is disabled, you won't be able to generate or retrieve their access token. Enable the user account if necessary to proceed.

  3. Generate or View Tokens:

    • Generate New Token: If the user does not have an access token or you need to generate a new one (perhaps due to token expiration or security reasons), look for the option to generate a new token. Clicking the "generate new token" button will create a new access token for the user.
    • View Current Tokens: If the user already has one or more access tokens and you just need to retrieve them, click on the "view tokens" button. This will display all current tokens associated with the user. In some interfaces, such as Martini Desktop, tokens may be automatically visible in the user's dialog, simplifying this step.
  4. Copy the Token: Once the token is generated or displayed, use the copy button to copy the access or refresh token to your clipboard. This token can then be used to authenticate API requests on behalf of the user.

Considerations

  • Token Expiry: Access tokens typically have a limited lifespan for security purposes. Be aware of the token's expiry and regenerate tokens as needed.
  • Secure Token Storage: Ensure that tokens are stored securely to prevent unauthorized access. Treat access tokens with the same level of security as passwords.
  • Permission Management: Only generate access tokens for users who require them. Manage user permissions and group memberships carefully to ensure that users have the appropriate level of access.

Viewing Applicable Rules

In Martini, managing how users interact with your APIs involves understanding and applying various rules, including those for throttling and monitoring. The "Rules" list provides a comprehensive overview of all such rules that apply to the user currently selected, ensuring you can effectively manage access and usage according to your application's needs.

Understanding Rule Types

  • Throttling Rules: These are indicated by a suffix "(Throttling)" and are designed to limit the number of requests a user can make to your APIs within a given timeframe. Throttling is crucial for preventing abuse and ensuring equitable resource distribution among all users.
  • Monitoring Rules: With a suffix "(Monitoring)," these rules are focused on observing and recording the user's activities. Monitoring rules help in analytics, identifying usage patterns, and potential security issues.

Adding a Group

Groups are essential for organizing users and applying collective policies, such as rules mentioned above. Here's how to add a group:

  1. Initiate Group Creation: Click the green '+' button to start the process of adding a new group.
  2. Specify Group Name: Enter a unique name for the group. This name will be used to identify the group within Martini and cannot be duplicated.
  3. Finalize: Click "Save" to apply the changes without closing the dialog or "OK" to save and close the dialog immediately.

Including a User in a Group

To include a user in one or more groups: - Edit the user's details and ensure that the checkboxes for the group(s) they should belong to are selected. This step integrates the user into the group, enabling them to inherit any rules or permissions assigned to the group.

Removing a Group

If a group is no longer needed, it can be removed by following these steps:

  1. Select the Group: Highlight the group you intend to remove.
  2. Initiate Removal: Click the red 'x' button. This action prompts for confirmation to ensure the removal is intentional.
  3. Confirm Action: Confirm your decision to delete the group. This permanently removes the group and any specific rules or permissions associated with it.

Save vs. OK

When managing groups or user details, the "Save" and "OK" options serve slightly different purposes: - Save: Applies the changes and keeps the dialog open, allowing for further modifications if needed. - OK: Applies the changes and closes the dialog, useful for when you're done with adjustments and ready to exit.

Basic Authentication

Basic authentication is a straightforward method for securing HTTP requests. It involves sending an Authorization header that contains a Base64-encoded string of the user's username and password, prefixed by the term "Basic". This technique is widely recognized for its simplicity in implementation and use. To illustrate, authenticating with the credentials demo as the username and s3cr3t as the password, the HTTP header would look like this:

1
Authorization: Basic ZGVtby9zM2NyM3Q=

Configuration in Martini

Martini provides built-in support for basic authentication. By default, this feature is turned off to encourage best practices for secure communications. Given that Base64 encoding can be easily decoded, enabling basic authentication is recommended only when HTTPS/SSL is configured to ensure encrypted communications between the client and server.

To activate basic authentication in Martini, you need to set a specific application property within your configuration. This enables the server to challenge clients for authentication credentials:

1
api.rest.enable-basic-authentication=true

Applying Configuration Changes

For the activation of basic authentication to take effect, Martini requires a restart. This step ensures that the api.rest.enable-basic-authentication property is reloaded and applied correctly. Restarting the instance is a critical step in ensuring that your security settings are properly initialized and enforced.

OAuth 2.0

OAuth 2.0 is a robust authorization framework that enables third-party services to exchange web resources on behalf of a user. It's designed to work over HTTPS and provides authorization flows for web, desktop applications, mobile phones, and living room devices. This protocol allows users to authorize third-party access to their server resources without sharing their credentials. It supports several grant types for different use cases, among which Martini specifically supports the password and refresh token grant types.

Acquiring Access Tokens

To interact with protected resources, clients must first obtain an access token that proves their authorization to access those resources. Access tokens are designed to be short-lived, typically expiring after an hour to ensure security. Upon expiration, a new token must be obtained, either through re-authentication or by using a refresh token.

Password Grant

The password grant type is used when the application exchanges a user's username and password for an access token directly. Here's how to request an access token using this grant type:

1
2
3
4
5
curl -X POST \
  http://localhost:8080/oauth/token \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password&username=demo&password=s3cr3t&client_id=TOROMartini&client_secret=TOROMartini'

This request will return an access token along with a refresh token, token type, expiration, and scope:

1
2
3
4
5
6
7
8
9
{
    "access_token": "d07326a0-2b6a-4530-9a60-24cd1e3bff0a",
    "token_type": "bearer",
    "refresh_token": "d9bf6d4b-2fc5-4ec4-a435-c02e82310b73",
    "expires_in": 3589,
    "scope": "read",
    "token_issued_counter": 4,
    "last_token_issued_date": "Monday, March 26, 2018 4:34:52 PM PHT"
}

Refresh Token Grant

Martini supports the use of refresh tokens, which are longer-lived than access tokens and can be used to obtain a new access token without requiring the user's credentials again. To use a refresh token for a new access token:

1
2
3
4
5
curl -X POST \
  http://0.0.0.0:8080/oauth/token \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=refresh_token&refresh_token=d9bf6d4b-2fc5-4ec4-a435-c02e82310b73&client_id=TOROMartini&client_secret=TOROMartini'

Accessing Protected Resources

Once you have an access token, you can use it to access protected resources by including it in the Authorization header of your requests, prefixed with "Bearer":

1
2
3
4
curl -X GET \
  http://localhost:8080/api/some/protected/resource \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer d07326a0-2b6a-4530-9a60-24cd1e3bff0'

Custom Client Configuration

Martini allows the specification of custom client identifiers and secrets through application properties oauth.client-id and oauth.client-secret. The default values for these properties are TOROMartini, matching the client_id and client_secret values used in the examples above.

By understanding and implementing these OAuth 2.0 flows within Martini, developers can securely manage access to resources, ensuring that applications operate within a secure and user-consented framework.

Securing a Groovy API

In the context of Martini, securing HTTP APIs created with Groovy is streamlined with the integration of Spring MVC and Spring Security, enabling the deployment of method-level security protocols. This guide will walk you through the process of securing a simple Groovy API endpoint using Spring Security's @Secured annotation, as well as integrating Basic and OAuth 2.0 authentication mechanisms.

Getting Started

To illustrate, let's start with a Groovy-based API:

1
2
3
4
5
6
7
8
9
@RequestMapping(value = "lannister")
class HouseLannister {

    @ResponseBody
    @RequestMapping(value = 'welcome', method = RequestMethod.PUT)
    def welcome() {
        return "Welcome home"
    }
}

This code snippet defines an HTTP endpoint (/api/lannister/welcome) that returns a simple greeting message. To test this endpoint, you can use the following cURL command:

1
curl -X PUT 'http://localhost:8080/api/lannister/welcome'

Expected response:

1
Welcome home

Securing the Endpoint

To restrict access to this endpoint, we implement the @Secured annotation, specifying which users or roles are allowed to invoke the method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.security.Principal

@RequestMapping(value = "lannister")
class HouseLannister {

    @Secured("ROLE_USER") // Adjust the role according to your security setup
    @ResponseBody
    @RequestMapping(value = 'welcome', method = RequestMethod.PUT)
    def welcome(Principal principal) {
        return "Welcome home, ${principal.name}"
    }
}

This modification ensures that only authenticated users with the specified role can access the endpoint. If an unauthorized user attempts to access it, they will receive an error:

1
2
3
4
5
<APIException>
  <result>ERROR</result>
  <apiErrorCode>-1</apiErrorCode>
  <message>Access is denied</message>
</APIException>

Authenticating Requests

To access the secured endpoint, authenticated requests are necessary. This can be achieved using OAuth 2.0 tokens or Basic authentication, depending on your application's configuration.

Using OAuth 2.0:

1
2
3
curl -X PUT \
  http://localhost:8080/api/lannister/welcome \
  -H 'Authorization: Bearer {ACCESS_TOKEN}'

Replace {ACCESS_TOKEN} with a valid token to receive the authenticated response:

1
Welcome home, Jaime

Using Basic Authentication:

If Basic authentication is enabled, the same endpoint can be accessed as follows:

1
2
curl -X PUT -u username:password \
  http://localhost:8080/api/lannister/welcome

Replace username and password with your credentials to authenticate and access the endpoint.

Access Control Lists

For more complex security requirements, organizing permissions into a cohesive group often simplifies management. This approach becomes particularly useful when multiple users need access to the same resources or functionalities within your application. Access Control Lists (ACLs) serve this purpose by grouping permissions, which can then be assigned en masse rather than individually.

Example Scenario: Expanding Access

Let's expand upon our previous example by allowing multiple users access to the /api/lannister/welcome endpoint. Suppose we now want the following users to have access:

  • Jaime
  • Cersei
  • Tyrion
  • Tywin

Instead of individually securing the endpoint for each user, we create a user group named Lannister. This group contains all the users mentioned above. We then utilize this group within our security configuration as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.security.Principal

@RequestMapping(value = "lannister")
class HouseLannister {

    @Secured("Lannister") // Restricts access to users in the 'Lannister' group
    @ResponseBody
    @RequestMapping(value = 'welcome', method = RequestMethod.PUT)
    def welcome(Principal principal) {
        return "Welcome home, ${principal.name}"
    }
}

By specifying @Secured("Lannister"), we limit access to the endpoint exclusively to users within the Lannister group. This simplifies permission management, especially as the number of users or roles increases.

Class-Level Security Annotation

Similar to how @RequestMapping can be applied at the class level to define routing for an entire controller, the @Secured annotation can also be declared at the class level. Applying @Secured in this manner enforces access control across all methods within the class, ensuring a consistent security posture:

1
2
3
4
5
@Secured("Lannister") // Apply security to all methods in the class
@RequestMapping(value = "lannister")
class HouseLannister {
    // All methods here inherit the class-level security constraint
}
Integration with Martini Runtime Edition

In the context of Martini's Runtime Edition, additional considerations apply. For instance, accessing a secured endpoint through a browser with a user already logged into the Runtime Admin UI is permitted. This behavior stems from the Marketplace credentials, which typically possess broader authorization scopes compared to regular Martini user accounts. It's an essential factor to consider, especially when dealing with access controls in environments where multiple types of users interact with your application.

Third-Party Authentication and Authorization

Leveraging third-party authentication and authorization services such as Amazon Cognito and Auth0, combined with an API gateway, enhances the security of your APIs. This approach is particularly beneficial if you have an existing authentication server or require scalable, independent authentication and authorization mechanisms. Third-party services often support federated identity through protocols like OpenID Connect and allow for integration with various social identity providers.

In this guide, we'll explore how to use Amazon Cognito for authentication and authorization, with Amazon API Gateway serving as the API's entry point. The API Gateway will verify user permissions by communicating with Amazon Cognito before granting access to the API.

Prerequisites

Before we begin, ensure you have the following:

  • Amazon Cognito User Pool: Necessary for managing user authentication. Refer to Amazon's Getting Started with User Pools guide for instructions on setting up a user pool.
  • A REST API: You'll need an existing REST API that you want to secure. While specific instructions on creating and exposing a REST API are beyond the scope of this document, Amazon provides comprehensive guides on publishing a RESTful API.

Creating and Adding a User to Amazon Cognito User Pool

To secure access to your API, you first need to create and add a user to your Amazon Cognito User Pool. This user will authenticate against the pool to gain access to the API.

Adding a User

  1. Navigate to the Amazon Cognito console and select Manage User Pools. Then choose your user pool from the list.
  2. In the General settings tab, select Users and groups.
  3. Click Create user. In the form that appears, enter the user's details. Ensure you remember the username and password, as they will be used for API access authentication.

Creating an Application Client

An application client acts as an intermediary that requests access to the Cognito User Pool on behalf of the user. This is essential in the OAuth 2.0 authentication flow.

  1. Within the same user pool dashboard, click App clients under General Settings.
  2. Select Add an app client.
  3. Provide a name for the app client and adjust other settings as required for your application's needs.
  4. Click Create app client. Note the App client ID that is generated, as it will be used in later steps.
  5. Go to App integration > Domain name and set a domain for the Cognito's Hosted UI, enabling user authentication through a web interface.

Forwarding Requests to the REST API

To enable Amazon API Gateway to forward requests to your REST API, follow these steps:

  1. Open the Amazon API Gateway Console and create an HTTP API by selecting Build.
  2. For integration, click Add integration and choose HTTP.
  3. Enter your API's endpoint URL.
  4. Provide an API name, review your settings, and click Create.
  5. Test the API integration by using the Invoke URL provided by API Gateway.

Integrating Amazon Cognito with Amazon API Gateway

To secure your API, configure API Gateway to request authorization from Amazon Cognito, ensuring only authenticated users can access the API.

  1. In the API Gateway console, select Authorization under the Develop tab.
  2. Navigate to Manage Authorizers and click Create.
  3. Choose JWT as the authorizer type.
  4. Name the authorizer and enter the issuer URL of your Amazon Cognito User Pool. This URL follows the format https://cognito-idp.{region}.amazonaws.com/{userPoolId}, where {region} and {userPoolId} correspond to your Cognito instance's details.
  5. Include the App client ID you noted earlier in the audience field and create the authorizer.
  6. To attach the authorizer to routes, click Attach authorizers to routes, select the route, choose your authorizer from the dropdown, and attach it.

Testing your configuration with an unauthorized request should now return a 401 Unauthorized response, indicating that the API Gateway correctly requests authentication from Amazon Cognito.

1
{"message":"Unauthorized"}

This step-by-step guide outlines the process for setting up a user and application client in Amazon Cognito, forwarding requests through Amazon API Gateway, and securing your API with Cognito authentication.

Authorizing a Request

Securely accessing your API requires obtaining a valid access token from Amazon Cognito, which involves initiating an OAuth 2.0 authentication flow. Depending on your application's nature, the specific OAuth flow to adopt can vary. For instance, web applications, mobile applications, and server-to-server applications might use different flows due to their unique security considerations and user interaction capabilities. Auth0 and other platforms offer comprehensive guides to help decide which OAuth flow suits your application best. In this tutorial, we will focus on the Authorization Code Flow, commonly used for applications that can securely store credentials.

Configuring OAuth Tools

OAuth Tools are instrumental in testing and demonstrating how OAuth flows operate. To simulate the Authorization Code Flow and retrieve an access token for our Cognito User Pool, follow these steps:

  1. Setting Up an Environment in OAuth Tools:

    • Navigate to your OAuth Tools of choice and locate the option to manage environments, typically under a section labeled "Environments."
    • Create a new environment by clicking New environment or a similar button.
    • Enter the issuer URL of your Amazon Cognito instance in the designated field and initiate the discovery process by selecting Discover or a similar option. This process should automatically fetch and fill in the necessary Cognito authorization endpoints.
  2. Registering a New Client:

    • Within the environments setup, proceed to the Client section and create a new client by selecting New client or equivalent.
    • Input the Application Client ID and Client Secret that were generated when you created your app client in Amazon Cognito. These credentials are critical for the OAuth flow and must be securely stored.
    • Ensure that the Authorization Code Flow is enabled for this client setup, as this flow requires an authorization code that is exchanged for an access token in a secure server-side request.
  3. Initiating the Authorization Code Flow:

    • With the environment and client configured, you can now initiate the Authorization Code Flow directly from OAuth Tools. This typically involves generating an authorization request that redirects to the Cognito hosted UI for user authentication.
    • Upon successful authentication, the service redirects back to a specified redirect URI with an authorization code.
    • The OAuth Tools should then automatically or manually use this code to request an access token from Cognito's token endpoint.

By configuring OAuth Tools and initiating the Authorization Code Flow, developers can test and understand the process of securing API access with Amazon Cognito. This setup illustrates the critical step of authorizing requests to your API, ensuring that only authenticated users can access your services.

Getting an Access Token

To securely access your API, obtaining an access token via the OAuth 2.0 Authorization Code Flow is essential. This token validates the user's identity and authorizes API requests.

Initiating the Code Flow

  1. Start the Authorization Code Flow in your OAuth Tools by selecting Start Flow and choosing Code Flow.
  2. Switch to the environment you previously configured for your Amazon Cognito instance.
  3. In the Amazon Cognito App client settings, add the OAuth Tools' callback URL to the list of allowed callback URLs. This step is crucial for the redirection process after authentication.
  4. Ensure the Cognito User Pool is enabled as an identity provider. Enable the Authorization Code Grant along with the openid scope in the Cognito settings and save your changes.
  5. In the OAuth Tools settings, select your app client and include the openid scope.
  6. Execute the flow by clicking Run. You'll be directed to the Cognito hosted UI for authentication.
  7. Enter the credentials for the user created in Cognito. If prompted, update the user's password. Cognito will redirect back to OAuth Tools upon successful sign-in.
  8. In OAuth Tools, find and click Redeem code to exchange the authorization code for an access token. Copy this access token for future requests.

Performing an Authorized Request

To make an API request with the access token:

  1. Use an HTTP client capable of setting custom headers, such as Postman or a similar tool.
  2. Configure a new request with your API's URL.
  3. In the request headers, add an Authorization header with the value Bearer {access_token}, replacing {access_token} with the actual token you copied earlier.
  4. Send the request. The API should respond as expected, given the request is authorized.

Retrieving User's Identity

To maintain a robust audit trail, embedding the user's identity in API calls is recommended. Here's how you can achieve this with tokens:

  • For Signed JWT Tokens: If your tokens are signed JWTs, the user's identity is typically encoded within the token. With support for OpenID Connect, this information is found in the ID token, which is also obtained through the OAuth flow. Most API gateways and applications, including Martini, can decode this token to extract user details automatically. Use a function like IdentityMethods.getCurrentUser() to access the user's identity directly in your application.

  • For Encrypted JWT Tokens: If the tokens are encrypted, you might need additional steps to decrypt them before use. This could involve configuring a service within the API gateway to decrypt the token, or setting up your application (e.g., Martini) to decrypt the token upon receiving it. Utilizing a client library for decryption is often necessary, which might involve importing specific cryptographic libraries.

This process ensures secure access to your API, leveraging OAuth 2.0 flows for authentication and authorization, while also facilitating the retrieval and utilization of the user's identity for auditing and personalized API responses.