Custom Authentication

View Source

barrel_mcp provides a simplified way to integrate custom authentication systems via barrel_mcp_auth_custom. This is useful when you have an existing key management system and want to use it for MCP HTTP authentication.

Simple Interface

Instead of implementing the full barrel_mcp_auth behaviour (with init/1, authenticate/2, and challenge/2), you only need two functions:

-module(my_auth).
-export([init/1, authenticate/2]).

%% Initialize authentication state
-spec init(Opts :: map()) -> {ok, State :: term()}.
init(_Opts) ->
    {ok, #{}}.

%% Authenticate a token
-spec authenticate(Token :: binary(), State :: term()) ->
    {ok, AuthInfo :: map(), NewState :: term()} |
    {error, Reason :: term(), NewState :: term()}.
authenticate(Token, State) ->
    case my_key_store:validate(Token) of
        {ok, KeyInfo} ->
            AuthInfo = #{
                subject => maps:get(user_id, KeyInfo),
                scopes => maps:get(permissions, KeyInfo, [])
            },
            {ok, AuthInfo, State};
        error ->
            {error, invalid_token, State}
    end.

Usage

Configure the HTTP server to use your custom auth module:

barrel_mcp:start_http(#{
    port => 9090,
    auth => #{
        provider => barrel_mcp_auth_custom,
        provider_opts => #{
            module => my_auth,
            module_opts => #{} % passed to my_auth:init/1
        }
    }
}).

Token Extraction

barrel_mcp_auth_custom automatically extracts tokens from HTTP requests. It checks (in order):

  1. Authorization: Bearer <token> header
  2. X-API-Key: <token> header

The extracted token is passed to your authenticate/2 function.

AuthInfo Format

Your authenticate/2 function should return a map with:

KeyTypeDescription
subjectbinaryUser/client identifier
scopes[binary]List of permission scopes

Additional keys are preserved in the claims field.

Example: barrel_memory Integration

Here's how barrel_memory uses custom auth with its existing key system:

-module(barrel_memory_mcp_auth).
-export([init/1, authenticate/2]).

init(Opts) ->
    {ok, Opts}.

authenticate(Token, State) ->
    case barrel_memory_api_keys:validate_key(Token) of
        {ok, KeyInfo} ->
            AuthInfo = #{
                subject => maps:get(team_id, KeyInfo, <<"unknown">>),
                scopes => maps:get(permissions, KeyInfo, [])
            },
            {ok, AuthInfo, State};
        {error, invalid_key} ->
            {error, invalid_token, State}
    end.

Configuration in barrel_memory:

barrel_mcp:start_http(#{
    port => 9091,
    auth => #{
        provider => barrel_mcp_auth_custom,
        provider_opts => #{
            module => barrel_memory_mcp_auth
        }
    }
}).

Claude Code Configuration

Once your MCP server is running with custom auth, add it to Claude Code:

# With Bearer token
claude mcp add my-server --transport http http://localhost:9090/mcp \
  --header "Authorization: Bearer your-api-key"

# Or with X-API-Key header
claude mcp add my-server --transport http http://localhost:9090/mcp \
  --header "X-API-Key: your-api-key"

Error Handling

When authentication fails, barrel_mcp_auth_custom returns a 401 response with:

WWW-Authenticate: Bearer realm="mcp"

Your authenticate/2 can return any error reason - it will be logged but not exposed to clients.