mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
61d0427898
Signed-off-by: dusan <borovcanindusan1@gmail.com>
449 lines
21 KiB
Markdown
449 lines
21 KiB
Markdown
# Auth - Authentication and Authorization service
|
|
|
|
Auth service provides authentication features as an API for managing authentication keys as well as administering groups of entities - `clients` and `users`.
|
|
|
|
## Authentication
|
|
|
|
User service is using Auth service gRPC API to obtain login token or password reset token. Authentication key consists of the following fields:
|
|
|
|
- ID - key ID
|
|
- Type - one of the three types described below
|
|
- IssuerID - an ID of the Magistrala User who issued the key
|
|
- Subject - user ID for which the key is issued
|
|
- IssuedAt - the timestamp when the key is issued
|
|
- ExpiresAt - the timestamp after which the key is invalid
|
|
|
|
There are four types of authentication keys:
|
|
|
|
- Access key - keys issued to the user upon login request
|
|
- Refresh key - keys used to generate new access keys
|
|
- Recovery key - password recovery key
|
|
- API key - keys issued upon the user request
|
|
- Invitation key - keys used to invite new users
|
|
|
|
Authentication keys are represented and distributed by the corresponding [JWT](jwt.io).
|
|
|
|
User keys are issued when user logs in. Each user request (other than `registration` and `login`) contains user key that is used to authenticate the user.
|
|
|
|
API keys are similar to the User keys. The main difference is that API keys have configurable expiration time. If no time is set, the key will never expire. For that reason, API keys are _the only key type that can be revoked_. This also means that, despite being used as a JWT, it requires a query to the database to validate the API key. The user with API key can perform all the same actions as the user with login key (can act on behalf of the user for Client, Channel, or user profile management), _except issuing new API keys_.
|
|
|
|
Recovery key is the password recovery key. It's short-lived token used for password recovery process.
|
|
|
|
For in-depth explanation of the aforementioned scenarios, as well as thorough understanding of Magistrala, please check out the [official documentation][doc].
|
|
|
|
The following actions are supported:
|
|
|
|
- create (all key types)
|
|
- verify (all key types)
|
|
- obtain (API keys only)
|
|
- revoke (API keys only)
|
|
|
|
## Domains
|
|
|
|
Domains are used to group users and clients. Each domain has a unique `route` that is associated with the domain. Domains are used to group users and their entities.
|
|
|
|
Domain consists of the following fields:
|
|
|
|
- ID - UUID uniquely representing domain
|
|
- Name - name of the domain
|
|
- Tags - array of tags
|
|
- Metadata - Arbitrary, object-encoded domain's data
|
|
- Route - unique route of the domain used in messaging
|
|
- CreatedAt - timestamp at which the domain is created
|
|
- UpdatedAt - timestamp at which the domain is updated
|
|
- UpdatedBy - user that updated the domain
|
|
- CreatedBy - user that created the domain
|
|
- Status - domain status
|
|
|
|
## Configuration
|
|
|
|
The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values.
|
|
|
|
| Variable | Description | Default |
|
|
| :------------------------------- | :----------------------------------------------------------------------------------- | :----------------------------- |
|
|
| `MG_AUTH_LOG_LEVEL` | Log level for the Auth service (debug, info, warn, error) | info |
|
|
| `MG_AUTH_DB_HOST` | Database host address | localhost |
|
|
| `MG_AUTH_DB_PORT` | Database host port | 5432 |
|
|
| `MG_AUTH_DB_USER` | Database user | magistrala |
|
|
| `MG_AUTH_DB_PASSWORD` | Database password | magistrala |
|
|
| `MG_AUTH_DB_NAME` | Name of the database used by the service | auth |
|
|
| `MG_AUTH_DB_SSL_MODE` | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
|
|
| `MG_AUTH_DB_SSL_CERT` | Path to the PEM encoded certificate file | "" |
|
|
| `MG_AUTH_DB_SSL_KEY` | Path to the PEM encoded key file | "" |
|
|
| `MG_AUTH_DB_SSL_ROOT_CERT` | Path to the PEM encoded root certificate file | "" |
|
|
| `MG_AUTH_HTTP_HOST` | Auth service HTTP host | "" |
|
|
| `MG_AUTH_HTTP_PORT` | Auth service HTTP port | 8189 |
|
|
| `MG_AUTH_HTTP_SERVER_CERT` | Path to the PEM encoded HTTP server certificate file | "" |
|
|
| `MG_AUTH_HTTP_SERVER_KEY` | Path to the PEM encoded HTTP server key file | "" |
|
|
| `MG_AUTH_GRPC_HOST` | Auth service gRPC host | "" |
|
|
| `MG_AUTH_GRPC_PORT` | Auth service gRPC port | 8181 |
|
|
| `MG_AUTH_GRPC_SERVER_CERT` | Path to the PEM encoded gRPC server certificate file | "" |
|
|
| `MG_AUTH_GRPC_SERVER_KEY` | Path to the PEM encoded gRPC server key file | "" |
|
|
| `MG_AUTH_GRPC_SERVER_CA_CERTS` | Path to the PEM encoded gRPC server CA certificate file | "" |
|
|
| `MG_AUTH_GRPC_CLIENT_CA_CERTS` | Path to the PEM encoded gRPC client CA certificate file | "" |
|
|
| `MG_AUTH_SECRET_KEY` | String used for signing tokens | secret |
|
|
| `MG_AUTH_ACCESS_TOKEN_DURATION` | The access token expiration period | 1h |
|
|
| `MG_AUTH_REFRESH_TOKEN_DURATION` | The refresh token expiration period | 24h |
|
|
| `MG_AUTH_INVITATION_DURATION` | The invitation token expiration period | 168h |
|
|
| `MG_AUTH_CACHE_URL` | Redis URL for caching PAT scopes | redis://localhost:6379/0 |
|
|
| `MG_AUTH_CACHE_KEY_DURATION` | Duration for which PAT scope cache keys are valid | 10m |
|
|
| `MG_SPICEDB_HOST` | SpiceDB host address | localhost |
|
|
| `MG_SPICEDB_PORT` | SpiceDB host port | 50051 |
|
|
| `MG_SPICEDB_PRE_SHARED_KEY` | SpiceDB pre-shared key | 12345678 |
|
|
| `MG_SPICEDB_SCHEMA_FILE` | Path to SpiceDB schema file | ./docker/spicedb/schema.zed |
|
|
| `MG_JAEGER_URL` | Jaeger server URL | <http://jaeger:4318/v1/traces> |
|
|
| `MG_JAEGER_TRACE_RATIO` | Jaeger sampling ratio | 1.0 |
|
|
| `MG_SEND_TELEMETRY` | Send telemetry to magistrala call home server | true |
|
|
| `MG_ADAPTER_INSTANCE_ID` | Adapter instance ID | "" |
|
|
| `MG_CALLOUT_URLS` | Comma-separated list of callout URLs | "" |
|
|
| `MG_CALLOUT_METHOD` | Callout method | POST |
|
|
| `MG_CALLOUT_TLS_VERIFICATION` | Enable TLS verification for callouts | true |
|
|
| `MG_CALLOUT_TIMEOUT` | Callout timeout | 10s |
|
|
| `MG_CALLOUT_CA_CERT` | Path to CA certificate file | "" |
|
|
| `MG_CALLOUT_CERT` | Path to client certificate file | "" |
|
|
| `MG_CALLOUT_KEY` | Path to client key file | "" |
|
|
| `MG_CALLOUT_OPERATIONS` | Invoke callout if the authorization permission matches any of the given permissions. | "" |
|
|
|
|
## Deployment
|
|
|
|
The service itself is distributed as Docker container. Check the [`auth`](https://github.com/absmach/magistrala/blob/main/docker/docker-compose.yaml) service section in docker-compose file to see how service is deployed.
|
|
|
|
Running this service outside of container requires working instance of the postgres database, SpiceDB, and Jaeger server.
|
|
To start the service outside of the container, execute the following shell script:
|
|
|
|
```bash
|
|
# download the latest version of the service
|
|
git clone https://github.com/absmach/magistrala
|
|
|
|
cd magistrala
|
|
|
|
# compile the service
|
|
make auth
|
|
|
|
# copy binary to bin
|
|
make install
|
|
|
|
# set the environment variables and run the service
|
|
MG_AUTH_LOG_LEVEL=info \
|
|
MG_AUTH_DB_HOST=localhost \
|
|
MG_AUTH_DB_PORT=5432 \
|
|
MG_AUTH_DB_USER=magistrala \
|
|
MG_AUTH_DB_PASSWORD=magistrala \
|
|
MG_AUTH_DB_NAME=auth \
|
|
MG_AUTH_DB_SSL_MODE=disable \
|
|
MG_AUTH_DB_SSL_CERT="" \
|
|
MG_AUTH_DB_SSL_KEY="" \
|
|
MG_AUTH_DB_SSL_ROOT_CERT="" \
|
|
MG_AUTH_HTTP_HOST=localhost \
|
|
MG_AUTH_HTTP_PORT=8189 \
|
|
MG_AUTH_HTTP_SERVER_CERT="" \
|
|
MG_AUTH_HTTP_SERVER_KEY="" \
|
|
MG_AUTH_GRPC_HOST=localhost \
|
|
MG_AUTH_GRPC_PORT=8181 \
|
|
MG_AUTH_GRPC_SERVER_CERT="" \
|
|
MG_AUTH_GRPC_SERVER_KEY="" \
|
|
MG_AUTH_GRPC_SERVER_CA_CERTS="" \
|
|
MG_AUTH_GRPC_CLIENT_CA_CERTS="" \
|
|
MG_AUTH_SECRET_KEY=secret \
|
|
MG_AUTH_ACCESS_TOKEN_DURATION=1h \
|
|
MG_AUTH_REFRESH_TOKEN_DURATION=24h \
|
|
MG_AUTH_INVITATION_DURATION=168h \
|
|
MG_SPICEDB_HOST=localhost \
|
|
MG_SPICEDB_PORT=50051 \
|
|
MG_SPICEDB_PRE_SHARED_KEY=12345678 \
|
|
MG_SPICEDB_SCHEMA_FILE=./docker/spicedb/schema.zed \
|
|
MG_JAEGER_URL=http://localhost:14268/api/traces \
|
|
MG_JAEGER_TRACE_RATIO=1.0 \
|
|
MG_SEND_TELEMETRY=true \
|
|
MG_AUTH_ADAPTER_INSTANCE_ID="" \
|
|
MG_CALLOUT_URLS="" \
|
|
MG_CALLOUT_METHOD="POST" \
|
|
MG_CALLOUT_TLS_VERIFICATION=true \
|
|
$GOBIN/magistrala-auth
|
|
```
|
|
|
|
Setting `MG_AUTH_HTTP_SERVER_CERT` and `MG_AUTH_HTTP_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key.
|
|
Setting `MG_AUTH_GRPC_SERVER_CERT` and `MG_AUTH_GRPC_SERVER_KEY` will enable TLS against the service. The service expects a file in PEM format for both the certificate and the key. Setting `MG_AUTH_GRPC_SERVER_CA_CERTS` will enable TLS against the service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs. Setting `MG_AUTH_GRPC_CLIENT_CA_CERTS` will enable TLS against the service trusting only those CAs that are provided. The service expects a file in PEM format of trusted CAs.
|
|
|
|
## Personal Access Tokens (PATs)
|
|
|
|
Personal Access Tokens (PATs) provide a secure way to authenticate with Magistrala APIs without using your primary credentials. They are particularly useful for automation, CI/CD pipelines, and integrating with third-party services.
|
|
|
|
### Overview
|
|
|
|
PATs in Magistrala are designed with the following features:
|
|
|
|
- **Scoped Access**: Each token can be limited to specific operations on specific resources
|
|
- **Expiration Control**: Set custom expiration times for tokens
|
|
- **Revocable**: Tokens can be revoked at any time
|
|
- **Auditable**: Track when tokens were last used
|
|
- **Secure**: Tokens are stored as hashes, not in plaintext
|
|
|
|
### Token Structure
|
|
|
|
A PAT consists of three parts separated by underscores:
|
|
|
|
```bash
|
|
pat_<encoded-user-and-pat-id>_<random-string>
|
|
```
|
|
|
|
Where:
|
|
|
|
- `pat` is a fixed prefix
|
|
- `<encoded-user-and-pat-id>` is a base64-encoded combination of the user ID and PAT ID
|
|
- `<random-string>` is a randomly generated string for additional security
|
|
|
|
### PAT Operations
|
|
|
|
Magistrala supports the following operations for PATs:
|
|
|
|
| Operation | Description |
|
|
| ----------- | ------------------------------------ |
|
|
| `create` | Create a new resource |
|
|
| `read` | Read/view a resource |
|
|
| `list` | List resources |
|
|
| `update` | Update/modify a resource |
|
|
| `delete` | Delete a resource |
|
|
| `share` | Share a resource with others |
|
|
| `unshare` | Remove sharing permissions |
|
|
| `publish` | Publish messages to a channel |
|
|
| `subscribe` | Subscribe to messages from a channel |
|
|
|
|
### Entity Types
|
|
|
|
PATs can be scoped to the following entity types:
|
|
|
|
| Entity Type | Description |
|
|
| ------------ | ---------------------- |
|
|
| `groups` | User groups |
|
|
| `channels` | Communication channels |
|
|
| `clients` | Client applications |
|
|
| `domains` | Organizational domains |
|
|
| `users` | User accounts |
|
|
| `dashboards` | Dashboard interfaces |
|
|
| `messages` | Message content |
|
|
|
|
### API Examples
|
|
|
|
#### Creating a PAT
|
|
|
|
```bash
|
|
curl --location 'http://localhost:9001/pats' \
|
|
--header 'Content-Type: application/json' \
|
|
--header 'Authorization: Bearer <access_token>' \
|
|
--data '{
|
|
"name": "test pat",
|
|
"description": "testing pat",
|
|
"duration": "24h"
|
|
}'
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"id": "a2500226-95dc-4285-87e2-e693e4a0a976",
|
|
"user_id": "user123",
|
|
"name": "pat 1",
|
|
"description": "for creating any client or channel",
|
|
"secret": "pat_dXNlcjEyM19hMjUwMDIyNi05NWRjLTQyODUtODdlMi1lNjkzZTRhMGE5NzY=_randomstring...",
|
|
"issued_at": "2025-02-27T11:20:59Z",
|
|
"expires_at": "2025-02-28T11:20:59Z"
|
|
}
|
|
```
|
|
|
|
#### Adding Scopes to a PAT
|
|
|
|
```bash
|
|
curl --location --request PATCH 'http://localhost:9001/pats/a2500226-95dc-4285-87e2-e693e4a0a976/scope/add' \
|
|
--header 'Content-Type: application/json' \
|
|
--header 'Authorization: Bearer <access_token>' \
|
|
--data '{
|
|
"scopes": [
|
|
{
|
|
"optional_domain_id": "c16c980a-9d4c-4793-8fb2-c81304cf1d9f",
|
|
"entity_type": "clients",
|
|
"operation": "create",
|
|
"entity_id": "*"
|
|
},
|
|
{
|
|
"optional_domain_id": "c16c980a-9d4c-4793-8fb2-c81304cf1d9f",
|
|
"entity_type": "channels",
|
|
"operation": "create",
|
|
"entity_id": "cfbc6936-5748-4339-a8ef-37b64b02bc96"
|
|
},
|
|
{
|
|
"entity_type": "dashboards",
|
|
"optional_domain_id": "c16c980a-9d4c-4793-8fb2-c81304cf1d9f",
|
|
"operation": "read",
|
|
"entity_id": "*"
|
|
}
|
|
]
|
|
}'
|
|
```
|
|
|
|
#### Listing PATs
|
|
|
|
```bash
|
|
curl --location 'http://localhost:9001/pats' \
|
|
--header 'Authorization: Bearer <access_token>'
|
|
```
|
|
|
|
#### Listing Scopes for a PAT
|
|
|
|
```bash
|
|
curl --location 'http://localhost:9001/pats/a2500226-95dc-4285-87e2-e693e4a0a976/scopes' \
|
|
--header 'Authorization: Bearer <access_token>'
|
|
```
|
|
|
|
#### Revoking a PAT
|
|
|
|
```bash
|
|
curl --location --request PATCH 'http://localhost:9001/pats/a2500226-95dc-4285-87e2-e693e4a0a976/revoke' \
|
|
--header 'Authorization: Bearer <access_token>'
|
|
```
|
|
|
|
#### Resetting a PAT Secret
|
|
|
|
```bash
|
|
curl --location --request PATCH 'http://localhost:9001/pats/a2500226-95dc-4285-87e2-e693e4a0a976/reset' \
|
|
--header 'Content-Type: application/json' \
|
|
--header 'Authorization: Bearer <access_token>' \
|
|
--data '{
|
|
"duration": "720h"
|
|
}'
|
|
```
|
|
|
|
### Using PATs for Authentication
|
|
|
|
When making API requests, include the PAT in the Authorization header:
|
|
|
|
```bash
|
|
Authorization: Bearer pat_<encoded-user-and-pat-id>_<random-string>
|
|
```
|
|
|
|
#### Example: Creating a Client Using PAT
|
|
|
|
```bash
|
|
curl --location 'http://localhost:9006/c16c980a-9d4c-4793-8fb2-c81304cf1d9f/clients' \
|
|
--header 'accept: application/json' \
|
|
--header 'Content-Type: application/json' \
|
|
--header 'Authorization: Bearer pat_etKoiXKTR6a0zdgsBHC00qJQAiaV3EKFh+Lmk+SgqXY=_u7@5fyjgti9V@#Bw^bS*SPmX3OnH=HTvKwmIbxIuyBjoI|6FASo9egjKD^u-M$b|2Dpt3CXZtv&4k+hmYYjk&C$57AV59P%-iDV0' \
|
|
--data '{
|
|
"name": "test client",
|
|
"tags": [
|
|
"tag1",
|
|
"tag2"
|
|
],
|
|
"metadata":{"units":"km"},
|
|
"status": "enabled"
|
|
}'
|
|
```
|
|
|
|
This example shows how to create a client in a specific domain (`c16c980a-9d4c-4793-8fb2-c81304cf1d9f`) using a PAT for authentication. The PAT must have the appropriate scope (e.g., `clients` entity type with `create` operation) for this domain.
|
|
|
|
### Wildcard Entity IDs
|
|
|
|
When defining scopes for PATs, you can use the wildcard character `*` for the `entity_id` field to grant permissions for all entities of a specific type. This is particularly useful for automation tasks that need to operate on multiple resources.
|
|
|
|
For example:
|
|
|
|
- `"entity_id": "*"` - Grants permission for all entities of the specified type
|
|
- `"entity_id": "specific-id"` - Grants permission only for the entity with the specified ID
|
|
|
|
Using wildcards should be done carefully, as they grant broader permissions. Always follow the principle of least privilege by granting only the permissions necessary for the intended use case.
|
|
|
|
### Scope Examples
|
|
|
|
#### Allow Creating Any Client in a Domain
|
|
|
|
```json
|
|
{
|
|
"optional_domain_id": "domain_id",
|
|
"entity_type": "clients",
|
|
"operation": "create",
|
|
"entity_id": "*"
|
|
}
|
|
```
|
|
|
|
This scope allows the PAT to create any client within the specified domain. The wildcard `*` for `entity_id` means the token can create any client, not just a specific one.
|
|
|
|
#### Allow Publishing to a Specific Channel
|
|
|
|
```json
|
|
{
|
|
"optional_domain_id": "domain_id",
|
|
"entity_type": "channels",
|
|
"operation": "publish",
|
|
"entity_id": "channel_id"
|
|
}
|
|
```
|
|
|
|
This scope restricts the PAT to only publish to a specific channel (`channel_id`) within the specified domain. No wildcard is used, so the permission is limited to just this one channel.
|
|
|
|
#### Allow Reading All Dashboards
|
|
|
|
```json
|
|
{
|
|
"optional_domain_id": "domain_id",
|
|
"entity_type": "dashboards",
|
|
"operation": "read",
|
|
"entity_id": "*"
|
|
}
|
|
```
|
|
|
|
This scope allows the PAT to read all dashboards within the specified domain. The wildcard `*` for `entity_id` means the token can read any dashboard in that domain.
|
|
|
|
### Best Practices
|
|
|
|
1. **Limit Scope**: Always use the principle of least privilege when creating PATs
|
|
2. **Set Expirations**: Use reasonable expiration times for tokens
|
|
3. **Rotate Regularly**: Reset token secrets periodically
|
|
4. **Audit Usage**: Monitor when tokens are used
|
|
5. **Revoke Unused**: Remove tokens that are no longer needed
|
|
|
|
### Implementation Details
|
|
|
|
PATs are stored in the database with the following schema:
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS pats (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
name VARCHAR(254) NOT NULL,
|
|
user_id VARCHAR(36),
|
|
description TEXT,
|
|
secret TEXT,
|
|
issued_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ,
|
|
updated_at TIMESTAMPTZ,
|
|
revoked BOOLEAN,
|
|
revoked_at TIMESTAMPTZ,
|
|
last_used_at TIMESTAMPTZ,
|
|
UNIQUE (id, name, secret)
|
|
)
|
|
|
|
CREATE TABLE IF NOT EXISTS pat_scopes (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
pat_id VARCHAR(36) REFERENCES pats(id) ON DELETE CASCADE,
|
|
optional_domain_id VARCHAR(36),
|
|
entity_type VARCHAR(50) NOT NULL,
|
|
operation VARCHAR(50) NOT NULL,
|
|
entity_id VARCHAR(50) NOT NULL,
|
|
UNIQUE (pat_id, optional_domain_id, entity_type, operation, entity_id)
|
|
)
|
|
```
|
|
|
|
### Authorization
|
|
|
|
When a PAT is used for authentication:
|
|
|
|
1. The system parses the token to extract the user ID and PAT ID
|
|
2. It verifies the token hasn't been revoked or expired
|
|
3. It checks if the requested operation is allowed by the token's scopes
|
|
4. If all checks pass, the operation is authorized
|
|
|
|
## Usage
|
|
|
|
For more information about service capabilities and its usage, please check out the [API documentation](https://docs.api.magistrala.absmach.eu/?urls.primaryName=auth.yaml).
|
|
|
|
[doc]: https://magistrala.absmach.eu/docs/ |