Setting Up n8n SSO with Self-Hosted Keycloak:

Step 1: Deploy Keycloak with Docker and PostgreSQL

1.1 Set Up PostgreSQL

Create a PostgreSQL instance for Keycloak’s data persistence.

mkdir -p ~/keycloak/postgres

Create a docker-compose.yml file for Keycloak and PostgreSQL:

version: '3.8'
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: secure_password
    volumes:
      - ./postgres:/var/lib/postgresql/data
    restart: unless-stopped
  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    command: start
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: secure_password
      KC_HOSTNAME: auth.example.com
      KC_PROXY: edge
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin_secure_password
    ports:
      - "8080:8080"
    depends_on:
      - postgres
    restart: unless-stopped

Save this as keycloak/docker-compose.yml. Replace secure_password and admin_secure_password with strong, unique values, and update auth.example.com to your Keycloak subdomain.

1.2 Configure Nginx for SSL

Set up Nginx as a reverse proxy to secure Keycloak with HTTPS.

Install Nginx:

sudo apt update
sudo apt install nginx

Create an Nginx configuration file:

sudo nano /etc/nginx/sites-available/keycloak

Add the following, replacing auth.example.com with your subdomain:

server {
    listen 80;
    server_name auth.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name auth.example.com;

    ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the configuration:

sudo ln -s /etc/nginx/sites-available/keycloak /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Obtain an SSL certificate using Certbot:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d auth.example.com

1.3 Start Keycloak

Launch the Keycloak stack:

cd ~/keycloak
docker-compose up -d

Access the Keycloak admin console at https://auth.example.com/admin, logging in with admin and admin_secure_password. Change the admin password immediately for security.

Step 2: Configure Keycloak as a SAML Identity Provider

Keycloak will act as the Identity Provider (IdP) for n8n. We’ll configure a SAML client, and set up mappers for user attributes.

Get required information from N8N

image - quochung.cyou PTIT
  1. Navigate to Settings -> SSO
  2. Save the Entity ID and Redirect URL

Create a SAML Client for n8n

  1. Navigate to Clients > Create Client.
  2. Set:
    • Client Type: SAML
    • Client ID: (n8n’s Entity ID from previous step, matching its base URL)
    • Name: n8n (Your choice)
  3. Configure:
    • Valid Redirect URIs: https://n8n.example.com/saml/acs (Redirect URI from previous step)
  4. Settings: (Image below)
    • Name ID format: Email
    • Force name ID format: ON
    • Force POST binding: ON
    • Include AuthnStatement: ON
    • Sign Document: ON
    • Sign Asseration: ON
    • Client Signature Required: Off
  5. Save the client.
image 1 - quochung.cyou PTIT
image 2 - quochung.cyou PTIT
image 3 - quochung.cyou PTIT

Configure Mappers

Mappers define how user attributes are sent to n8n.

  1. In the n8n client, go to Clients Scope > (The entity id, ex: https://yourdomain.com/rest/sso/saml/metadata-dedicated)
  2. Add mapper -> By configuration -> User property

2. Create the mapper like image, each one config look like this

image 5 - quochung.cyou PTIT
image 6 - quochung.cyou PTIT
image 7 - quochung.cyou PTIT
image 8 - quochung.cyou PTIT
image 4 - quochung.cyou PTIT

Export SAML Metadata

  1. Go to Realm Settings > SAML 2.0 Identity Provider Metadata.
  2. Download the XML file or copy the metadata URL (e.g., https://auth.example.com/realms/n8n-realm/protocol/saml/descriptor).
image 9 - quochung.cyou PTIT
image 10 - quochung.cyou PTIT

Setup the N8N use SAML metadata

image 11 - quochung.cyou PTIT

Edit metadata

Locate <md:IDPSSODescriptor WantAuthnRequestsSigned=”true” in the XML, and change true to false

Final gonna look like <md:IDPSSODescriptor WantAuthnRequestsSigned=”false”

Test Setting

  1. Open an incognito browser window and navigate to https://n8n.example.com.
  2. You should be redirected to Keycloak’s login page.
  3. Log in with a Keycloak user (create one in Users > Add User if needed, ensuring the email matches an n8n user).
  4. Upon successful authentication, you should be redirected to n8n’s dashboard.

Dynamic OAuth2 Client for Spring Boot

Introduction

OAuth2 has become the industry standard for secure authorization in web applications. Spring Boot simplifies OAuth2 integration, allowing developers to handle client registration effortlessly. However, Spring Boot’s default implementation often requires client registrations to be predefined in application configuration, making it difficult to adapt to changing environments where client registrations need to be added or removed dynamically.

In this blog post, we’ll explore how to overcome this limitation by creating a custom implementation of ClientRegistrationRepository that allows dynamic management of OAuth2 clients at runtime. This approach provides the flexibility required for modern applications that need to adapt to varying OAuth2 client configurations.

Understanding Spring Boot’s Default Client Registration

Spring Boot manages OAuth2 client registrations using the ClientRegistrationRepository interface, which provides a way to store and retrieve client configurations. By default, Spring Boot offers an InMemoryClientRegistrationRepository implementation. This default implementation stores client registrations in memory, as defined in the application’s configuration files.

While this approach works well for static configurations, it has a significant limitation: the client registrations are immutable. Once registered, a client’s configuration cannot be altered. This is because InMemoryClientRegistrationRepository creates an unmodifiable map of client registrations:

Java
	public InMemoryClientRegistrationRepository(List < ClientRegistration > registrations) {
	    this(createRegistrationsMap(registrations));
	}

	private static Map < String, ClientRegistration > createRegistrationsMap(List < ClientRegistration > registrations) {
	    Assert.notEmpty(registrations, "registrations cannot be empty");
	    return toUnmodifiableConcurrentMap(registrations);
	}

	private static Map < String, ClientRegistration > toUnmodifiableConcurrentMap(List < ClientRegistration > registrations) {
	    ConcurrentHashMap < String, ClientRegistration > result = new ConcurrentHashMap < > ();
	    for (ClientRegistration registration: registrations) {
	        Assert.state(!result.containsKey(registration.getRegistrationId()),
	            () - > String.format("Duplicate key %s", registration.getRegistrationId()));
	        result.put(registration.getRegistrationId(), registration);
	    }
	    return Collections.unmodifiableMap(result);
	}

This immutability is by design, ensuring that client registrations remain consistent throughout the application’s lifecycle. However, for applications that require dynamic updates to client registrations—such as SaaS platforms or systems with evolving client needs—this default behavior is insufficient.

Custom Implementation of ClientRegistrationRepository

To support dynamic client registration, we need to implement a custom version of ClientRegistrationRepository that allows adding and removing clients at runtime. This custom repository will manage a modifiable collection of client registrations.

Here’s how you can create a custom ClientRegistrationRepository:

Java
public class DynamicClientRegistrationRepository implements ClientRegistrationRepository {
    private final ConcurrentHashMap < String, ClientRegistration > clientRegistrations = new ConcurrentHashMap < > ();

    public void addClientRegistration(String registrationId, ClientRegistration registration) {
        clientRegistrations.put(registrationId, registration);
    }

    @Override
    public ClientRegistration findByRegistrationId(String registrationId) {
        return clientRegistrations.get(registrationId);
    }
}

This implementation uses a ConcurrentHashMap to store client registrations, enabling thread-safe access and modification. The addClientRegistration and removeClientRegistration methods allow for dynamic management of client registrations, which can be crucial for applications that need to adapt in real time.

Java
clientRegistrationRepository.addClientRegistration(clientRegistration.getRegistrationId(), clientRegistration);

Integrating Custom ClientRegistrationRepository with Spring Security

After implementing the custom ClientRegistrationRepository, the next step is to integrate it into your Spring Boot application. This involves replacing the default InMemoryClientRegistrationRepository with your custom implementation.

Here’s how to inject your custom repository into the Spring Security configuration:

Java
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build();
        clientRegistrationRepository = new DynamicClientRegistrationRepository();
        clientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
        var clientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
            clientRegistrationRepository, clientService);
        clientManager.setAuthorizedClientProvider(authorizedClientProvider);

By defining the custom ClientRegistrationRepository as a bean, Spring Boot will automatically use it in the OAuth2 flow, allowing for dynamic client management.

Acquiring Access Tokens Using Dynamic Client Registrations

Once you have dynamically registered clients, you can proceed with acquiring access tokens using the standard OAuth2 flow. The process remains unchanged, but now includes the capability to work with clients that were registered dynamically.

Here’s an example of acquiring an access token:

Java
        OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistration.getRegistrationId())
            .principal(oauth2Config.getClientId())
            .attribute(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue(), clientRegistration)
            .build();
        OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest);
        authorizedClient.getAccessToken().getTokenValue() //-> get token here

Tìm hiểu về session fixation – Vấn đề bảo mật trong authentication bằng cookie

Session cookies

  • Cách đơn giản nhất để triển khai authentication theo dạng token, và cũng là cách được triển khai rất phổ biến trong các website, là sử dụng cookie. Một luồng đơn giản bao gồm: Sau khi người dùng xác thực danh tính bằng tài khoản, mật khẩu, …, hệ thống sẽ trả về 1 header Set-Cookie ở response, yêu cầu trình duyệt sẽ lưu 1 token ngẫu nhiên trong bộ nhớ cookie. Các kết nối tiếp theo vào trang web sẽ có đính kèm header Cookie chứa token. Hệ thống sẽ kiểm tra xem có người dùng nào gắn với token này không.
image 41 - quochung.cyou PTIT

Ví dụ thực tế về session cookie

  • Bạn đến phòng gửi đồ, gửi đồ của mình gồm ví tiền, đồ cá nhân, …
  • Phòng gửi đồ cất đồ, và cho bạn 1 thẻ id là ID_NGUOI_DUNG_1, họ sẽ nhớ là với cái id này thì được lấy đống đồ kia
  • Lần sau bạn tới với id trên, phòng gửi đồ sẽ cho bạn quyền được sử dụng đồ theo id này. Hiển nhiên id sẽ được sinh 1 cách ngẫu nhiên và bảo mật để khó lòng đoán được trong thực tế.

Session Fixation

Case kẻ tấn công có thể sử dụng như ví dụ trên:

  • Hacker đầu tiên đến phòng gửi đồ, xưng danh và được cho thẻ ID là ID_NGUOI_DUNG_1
  • Hacker bằng cách nào đó khiến người dùng cầm thẻ ID đó đi gửi đồ, phòng gửi đồ sẽ cất các đồ vào ID_NGUOI_DUNG_1
  • Lúc này hacker dùng id kia đi lấy được đồ của người dùng.

Về việc làm sao để khiến người dùng cầm thẻ id đi gửi đồ thì có rất nhiều cách, có thể kể tới:

  • Hacker gửi cho bạn 1 đường link kèm id bên trong, người dùng khi click vào, hệ thống yêu cầu đăng nhập. Sau khi người dùng đăng nhập bằng tài khoản mật khẩu của mình, hệ thống lại gán id của hacker vào các thông tin có thể truy cập của người dùng
  • Tấn công theo subdomain. Ví dụ ta có trang web example.comhacker.example.com. Hacker có quyền kiểm soát ở subdomain, khi người dùng vào hacker.example.com, người dùng đã nhận được id được sinh ra của hacker, và khi đăng nhập ở example.com thì id đó sẽ được gán với thông tin của người dùng

  • Giả sử đây là một đoạn code triển khai cookie token và có thể bị dính Session Fixation
image 42 - quochung.cyou PTIT
image 43 - quochung.cyou PTIT
  • Để giải quyết vấn đề trên code trên. Rất đơn giản, ta cần đảm bảo khi người dùng đăng nhập, các session cũ phải bị huỷ đi và đảm bảo thông tin người dùng chỉ có thể gắn với 1 id được tạo mới, và hacker không đoán được.

Luồng như ví dụ thực tế:

  • Khi người dùng cầm id của hacker đến phòng gửi đồ để tạo id mới
  • phòng gửi đồ kiểm tra và thấy id này đã được tạo ra từ trước.
  • Phòng gửi đồ huỷ id cũ đi nếu đã tồn tại
  • Tạo id mới
image 44 - quochung.cyou PTIT

Các cách solution khác được suggest:

  • Solution: Utilize SSL / TLS session identifier: Các giao thức mã hoá SSL/TLS cũng sinh ra các session identifier để định danh. Ta có thể sử dụng luôn phần này vì nó đã được làm bảo mật mạnh. Tuy nhiên một số trình duyệt sẽ không hỗ trợ cách trên
  • Accept only server-generated SIDs: Các SID (session id) chỉ nên được sinh ra bởi server để hạn chế khả năng hacker tạo sid
  • Logout function: Ta có thể có các chức năng đăng xuất, rời trình duyệt, … khi đó sẽ huỷ luôn id để đảm bảo không có request nào khác được gửi vào
  • Verify that additional information is consistent throughout session: Thông thường thì khi người dùng sử dụng, họ chỉ sử dụng 1 địa chỉ ip, hoặc 1 địa chỉ mac, … tuỳ vào usecase. Ta có thể kiểm tra giữa các request có thông tin nào bị thay đổi không để tăng thêm khả năng bảo mật
  • Do not accept session identifiers from GET / POST variables: Không nên để SID vào trong các biến ở request, điều này khiến hacker dễ dàng tạo các url chứa sid độc để tấn công hơn.

Tham khảo:

[Phần 2] Session Token và JWT Token, Refresh Token, khi nào quyết định sử dụng, ưu và nhược điểm

This entry is part 2 of 2 in the series API Security

Bearer Token

image 19 - quochung.cyou PTIT
  • Lúc này, ta đã đang thực hiện giao tiếp với server qua token, có một câu hỏi đặt ra là, làm sao để gửi token đó cho phía server?
  • Hiển nhiên, ta có thể làm điều ở ở body của request, ở param, … hoặc ở cookie. Tuy nhiên, có một cách thông dụng và được sử dụng hầu hết hiện nay để chuyển 1 token không qua cookie đó là sử dụng dạng “Bearer” https://tools.ietf.org/html/rfc6750
  • Bearer lúc đầu được tạo ra nhằm phục vụ cho OAuth2 (sẽ nói thêm sau)
  • Để gửi 1 token qua api, ta chỉ cần để nó ở header Authorization
Authorization: Bearer QDAmQ9TStkDCpVK5A9kFowtYn2k
  • Việc sử dụng giao tiếp qua Authorization cũng cho phép ta sử dụng header WWW-Authenticate để giúp đầu api “thân thiện” với http hơn
  • WWW-Authenticate có thể theo kèm một tham số là “realm”, bạn có thể hiểu trường này như một trường nhằm định dang đầu api hiện tại. VD nếu realm = admin, thì client sẽ hiểu đầu api này cần token dành cho admin, nếu realm = users thì là các đầu api open cho user
  • Ngoài ra, ta cũng có thể thêm các thông báo về việc token hết hạn, sai, …
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="users", error="invalid_token",
        error_description="Token has expired"
  • Phần này giúp phía client có thể biết là token đã hết hạn và yêu cầu tạo token mới
image 20 - quochung.cyou PTIT
  • Để lưu token ở phía client mà không cần sử dụng cookie, ta có thể sử dụng các bộ nhớ có sẵn của trình duyệt như
    • sessionStorage: Các thực thể có thể được lưu tại đây, bộ nhớ chỉ là tạm thời và bị xoá khi tab bị đóng
    • localStorage: Dữ liệu được lưu kể cả khi trình duyệt đóng và chỉ xoá nếu ta yêu cầu
  • Nghe sessionStorage giống với cookie, tuy nhiên sessionStorage sẽ không share giữa các tab với nhau. Tức là cookie sẽ share giữa các tab, nhưng chỉ chung 1 domain (tức là phân biệt bằng tên miền). Còn sessionStorage chỉ lưu trạng thái ở tab hiện tại

JWT Token

  • Ta đã đi qua cách authentication bằng sử dụng basic authentication, rồi đến token được lưu ở cookie hoặc local/session storage. Tuy nhiên các cách thức này đều yêu cầu token cũng phải được lưu ở phía server.
  • Tức là mỗi truy vấn vào server, ta lại phải gọi vào database, với hàng triệu, hàng tỉ token ngày càng tăng lên để kiểm tra xem token có tồn tại chưa, có hết hạn chưa, … Chưa kể đến ta còn phải giải mã token ra nếu ta mã hoá nó trong database (thường là có)
  • Việc này tạo thành một bài toán lớn và ngày càng khó giải khi số lượng user tăng cao
image 21 - quochung.cyou PTIT
  • Lúc này, có một giải pháp khác đã được tạo ra, đó là JWT (Json Web Token)
  • Định nghĩa: JWT hay JSON Web Token là một format thông dụng cho các token ở dạng “self-contained” (chỉ lưu ở client). JWT thường bao gồm các claims (các khẳng định rằng user này có gì, có kia) và các quyền của user. VD, JWT sẽ chứa là user có quyền được vào trang a, trang b, ….
  • JWT ngoài ra được mã hoá crypto để bảo vệ khỏi các vấn đề bảo mật liên quan

Lưu JWT ở phía Client

  • Bạn sẽ có câu hỏi: Làm sao để một token chỉ có thể lưu ở client mà không ở server, vậy thì làm sao ta biết là token nào là thoả mãn, chưa bị người khác sửa, thêm quyền kiểu admin vào và cầm token đó đi request server?
image 22 - quochung.cyou PTIT
  • Một trong các cách được đề ra có thể xem qua ảnh sau. Tức là khi phía server sinh ra 1 token thoả mãn, ta sẽ thêm 1 key (HMAC) vào đuôi của token.
  • Hmac được sinh ra từ claims và 1 đoạn key, điều này giúp kiểm tra nếu đoạn claims đã bị sửa đổi thì phần HMAC sẽ không trùng khớp nữa.
  • Điều này đảm bảo là người khác khi sửa gì đó thì sẽ không thoả mãn nữa do ta có phần đuôi tag để kiểm tra xem. (Server sẽ thử giải mã phần key xem có đúng với ban đầu không, key này chỉ server biết)
  • Cấu trúc JWT có 3 phần
    • Header: Chứa các thông tin về kiểu token này dùng mã hoá dạng gì, …
    • Claims: Phần chứa thông tin ta muốn để trong token, ví dụ quyền của user
    • HMAC Tag: Phần mã hoá đảm bảo tính toàn vẹn của token
image 22 - quochung.cyou PTIT
  • JWT trở lên ngày càng phổ biến ở những năm gần đây do JSON Web Tokens đã được tiêu chuẩn hoá vào 2015. (Trước đây ta gọi những token stateless bên trên là JSON Token). JWT là một chuẩn với chức năng tương tự như ý tưởng JSON Tokens ban đầu, nhưng có một số chức năng thêm:
    • Một quy định chung cho phần header chứa các thông tin kèm cho JWT, ví dụ như thuật toán mã hoá, …
    • Một số chuẩn chung về phần claims, là trường nào lưu gì, …
    • Nhiều thuật toán khác nhau để mã hoá và xác thực.
  • Tuy rằng JWT chỉ là một quy chuẩn định nghĩa tại https://tools.ietf.org/html/rfc7519, nó được xây dựng từ một tập hợp nhiều quy chuẩn được gọi là JSON Object Signing and Encryption (JOSE). JOSE chứa nhiều tiêu chuẩn sau:
  • Một token JWT cơ bản trông như sau:
image 20 - quochung.cyou PTIT
  • Sự linh hoạt của JWT cũng là điểm yếu lớn nhất của nó, nhiều cuộc tấn công đã xảy ra nhờ vào sự linh hoạt này. JOSE là một tập các phần thiết kế cho phép lập trình viên chọn từ nhiều thuật toán khác nhau, và không phải sự kết hợp nào cũng đem lại tính bảo mật tốt.
  • VD: Vào năm 2015, (http://mng.bz/awKz) Tim McClean đã tìm ra một lỗ hổng trong rất nhiều thư viện JWT về việc ở phần header, ta có thể sửa nó thành thuật toán khác, và thậm chỉ sửa nó thành “none”. Điều này vô tình bảo các thư viện JWT ở phía backend khi kiểm tra token đã không dùng thuật toán nào để kiểm tra mã hoá, và các phần claims bị sửa trái phép không bị phát hiện. (Hay còn được biết đến là JWT None Attack)

Paseto

image 21 - quochung.cyou PTIT
  • Các lỗi trong các quy chuẩn dẫn tới sự phát triển của các công cụ thay thế với hi vọng vẫn chứa các tính năng tương tự nhưng sẽ sửa một số vấn đề của bộ cũ.
  • Một ví dụ là Paseto, cung cấp các trình mã hoá đối xứng, khoá public, … hỗ trợ xử lý các vấn đề của Jose và các chuẩn JWT chung.
  • Điểm chung ngắn gọn giữa Paseto và Jose là thay vì cho phép lập trình viên mix giữa nhiều cấu phần khác nhau trong bộ Jose, Paseto chứa một bộ fix cứng chung về thuật toán (AESS, RSA, Ed25519, …), giúp giảm thiểu các vấn đề bảo mật xảy ra.

Một số chuẩn chung trong claims của JWT

Thông thường, phần claims sẽ có các mục sau:

issIssuerChỉ ra là ai đã tạo ra token này, thường là url của trang đã sinh token.
audAudienceChỉ ra token này dành cho ai, có thể là id của user, …
iatIssued-AtThời điểm token được tạo ra
nbfNot-BeforeToken bị cấm nếu dùng trước thời gian này. VD đặt là 2/1 mà dùng vào 1/1 thì không được
expExpiryThời điểm token hết hạn
subSubjectĐịnh danh chủ đề của token, thường là username, id hay gì đó
jtiJWT IDID không bị trùng của token, dùng để tra cứu sau này nếu cần

JWT trong Database, xoá token

image 23 - quochung.cyou PTIT
  • Các token stateless, chỉ cần ở client side là một cách hay để giảm bớt state khỏi database. Điều này giúp ta scale các api mà không cần nâng cấp nhiều phần cứng database hay phải triển khai nhiều việc hơn.
  • Tuy nhiên, việc lưu trữ token ở database lại cho phép ta có một quyền năng lớn, đó là có thể revoke, hay quyết định 1 token có hợp lệ hay không, bằng cách chỉ cần xoá nó khỏi database. Hiển nhiên, việc lưu ở database đi kèm rất nhiều vấn đề về scale, có thể là ta cần nhiều database token cho nhiều service nhỏ khác nhau, nếu ta tập trung nó lại, nó có thể là 1 điểm SPOF (Single Point of Failure), 1 điểm nghẽn trong toàn luồng và chỉ cần nó chết, toàn hệ thống sẽ ngỏm do không có khả năng authentication

  • Có một số cách để xử lý vấn đề này. Ta có thể lưu database, tuy nhiên không lưu toàn bộ token mà chỉ cần 1 id unique nào đó mà có thể link với 1 token, khi huỷ hợp lệ 1 token, ta chỉ cần huỷ id đó, khi token đó được gửi bởi client, ta sẽ giải mã ra id và kiểm tra. Điều này giúp ta giảm bớt phần nào đó về database
  • Một cách khác là, ta có thể chỉ lưu các token bị huỷ hợp lệ, hay danh sách đen. Khi token được gửi lên ,ta xem nó có trong danh sách đen hay không, thì sẽ từ chối token đó. Các token mà đã hết hạn sử dụng rồi có thể clean up dần đi, giúp database danh sách đen nhỏ
  • Một cách khác là thay vì block 1 token, ta có thể chỉ chặn 1 số chức năng ở 1 tập hợp tk. Ví dụ, ta thường có chức năng đăng xuất ở mọi thiết bị khác khi ta đổi mật khẩu, để làm điều này, ta có thể thêm vào database toàn bộ token của người dùng trước ngày nào đó đều bị xoá, điều này giúp giảm không gian cần cho database, nhưng đi kèm query phức tạp hơn.

Tham khảo:

  • API Security in Action
  • Modern API Architecture

Còn tiếp

[Phần 1] Session Token và JWT Token, Refresh Token, khi nào quyết định sử dụng, ưu và nhược điểm

This entry is part 1 of 2 in the series API Security

Lời đầu

  • Trước khi đi vào Session Token hay JWT Token, hay có thể tóm gọn là authentication qua token, ta sẽ đi qua về 1 cách khác để authentication qua các giao thức API, và sau đó sẽ nói về ưu nhược điểm giữa chúng

Http Basic Authentication

  • Luồng chạy của HTTP “Basic” Authentication như sau:
image 14 - quochung.cyou PTIT
  • Khi người dùng truy cập và không có thông tin đăng nhập, hệ thống sẽ trả về 401 (Unauthorized) và thông tin đi kèm là cách để có thể author qua header WWW-Authenticate với thông tin (Ví dụ trên ảnh là yêu cầu Authen qua Basic)
  • Khi ta truy cập với Header Authorization + Basic + chuỗi mã, lúc này hệ thống sẽ kiểm tra và cho phép có quyền hay không.
 Authorization: Basic dGVzdDoxMjPCow==
  • Ví dụ như mã trên, đoạn mã sau Basic cơ bản là một đoạn String được mã hoá ở dạng base64, bạn có thể decode nó qua các trang base64, ví dụ
image 15 - quochung.cyou PTIT
  • Ví như với mã trên, khi decode, ta nhận được đoạn mã test:123£, đây là format của Basic Authen, với đầy đủ là username:password, thì ở đây test là username, và password là 123£

Ưu điểm

  • Sử dụng đơn giản, luồng không phức tạp, triển khai nhanh
  • Phù hợp cho authentication trong nội bộ, cho các ứng dụng trên môi trường kiểm thử (non-prod)

Nhược điểm

  • Toàn bộ request đều sẽ phải gửi username và password, điều này nhìn chung tăng khả năng xảy ra lộ mật khẩu.
  • Do lần nào cũng gửi mật khẩu, nên nếu bạn mã hoá nó, việc này là một công đoạn khá tốn tài nguyên. Những thuật toán hashing, mã hoá mật khẩu hiện đại có thể tốn tới 100ms (0,1s) để giải mã mật khẩu rồi kiểm tra có đúng không. Điều này hao tốn CPU cho hệ thống
  • Một số vấn đề khác về UX (giao diện mặc định đăng nhập xấu), vấn đề bảo mật về việc lưu password ở client để gửi đi authen, ….

Token Authentication trong API

Khái niệm

  • Hiểu đơn giản, thông thường thì authentication bằng token sẽ hoạt động như sau: Tài khoản, mật khẩu, … thông tin user chỉ cần nộp 1 lần, người dùng sau đó sẽ được cấp 1 token, 1 đoạn mã có thời hạn sử dụng. Người dùng sau đó có thể dùng token này để authentication cho đến khi đoạn token hết hạn sử dụng
  • Ví dụ về token authentication trong thực tế, điều này giống như bạn đi một khu vui chơi. Bạn đã đặt vé từ trước, và để xác minh bạn đã đặt vé chưa, bạn phải xuất trình căn cước công dân, ảnh thẻ, hộ chiếu, …. Thay vì vậy, khu vui chơi sau khi nhận căn cước công dân ở cửa vào, sẽ cấp cho bạn 1 phiếu vé có thời hạn 1 ngày hay gì đó, sau đó bạn dùng phiếu vé này để đi chơi trong khu vui chơi trong thời gian của vé.

Cách tiếp cận 1: Lưu trữ token trong database

image 16 - quochung.cyou PTIT
  • Người dùng lần đầu sẽ gửi thông tin của mình cho server, server sẽ từ đó sinh ra 1 token và lưu nó vào database
  • Lần sau người dùng sẽ chỉ cần gửi token, và sau đó server kiểm tra token có trong database không, còn hạn sử dụng không, … rồi cho phép người dùng

Việc này khắc phục các điểm:

  • Đầu tiên, token thường không chứa thông tin mật khẩu, tài khoản, … nữa nên ít lo bị lộ thông tin cá nhân của người dùng hơn.
  • Giảm bớt các nhược điểm của Basic Authen là phải mã hoá, giải mã mật khẩu, ….
  • Nếu có vấn đề bảo mật xảy ra, token được cài đặt là có thời hạn, và hơn nữa ta đã lưu ở trong database, ví dụ, bạn bị mất token vào tay người khác, thì thiệt hại cũng được giảm thiểu. Hoặc ta có thể trực tiếp ngăn chặn bằng cách xoá token đó đi trong database.

Session Cookie

  • Cách triển khai đơn giản nhất của hướng tiếp cận trên, và cũng khá phổ biến là sử dụng cookie. Sau khi người dùng đăng nhập bằng thông tin, phía server sẽ trả về 1 header Set-Cookie trong response để yêu cầu trình duyệt người dùng lưu 1 chuỗi token ngẫu nhiên trong bộ nhớ cookie của trình duyệt
  • Các request vào trang đó sẽ sử dụng token như 1 header Cookie (do cookie được tạo ra để lưu trữ những thông tin của người dùng trong 1 site)
image 18 - quochung.cyou PTIT

Ưu điểm

  • Cookie có nhiều cơ chế về bảo mật có sẵn, ví dụ như cookie chỉ gắn liền với từng tên miền, nên nó luôn đảm bảo không có chuyện ta gửi sang trang khác đoạn cookie chứa thông tin nhạy cảm
  • Cookie có thể được đánh dấu là Secure, điều này giúp đảm bảo khi gửi kết nối tới trang nào thì cookie chỉ được gửi nếu kết nối đó là Https và đã được mã hoá.

Nhược điểm

  • Trong thiết kế API theo hướng RestAPI đó là kết nối giữa client người dùng và server nên là stateless http://quochung.cyou/kien-truc-stateful-va-stateless-la-gi/. Đó là server sẽ không lưu trạng thái nào của người dùng giữa các request khác nhau.
  • Cookies phá vỡ nguyên tắc này vì server lúc này lưu trạng thái của cookie với các client khác nhau. Vào thời điểm đầu khi session cookie được sử dụng, chúng thường là nơi lưu trữ tạm thời 1 số trạng thái của người dùng, ví dụ như 1 giỏ hàng, 1 danh sách sản phẩm đã được chọn mà người dùng chưa thanh toán. (Kiểu khi người dùng chọn rồi, nó không lưu trực tiếp vào database) mà chỉ lưu vào cookie, điều này dễ gây ra một số lỗi bất thường cho website.
Cookie attributeÝ nghĩa
SecureCookie chỉ có thể gửi qua kết nối https
HttpOnlyKhông thể đọc trực tiếp bằng javascript
SameSiteChỉ gửi cho cùng trang
Một số attribute của cookie về bảo mật