---
title: "Add JWT Authentication to Your Cowboy API"
description: "Secure your Cowboy API using JWT authentication with JWKS."
canonicalUrl: "https://zuplo.com/use-cases/api-key-auth/erlang/cowboy/jwt-backend"
framework: "Cowboy"
language: "Erlang"
authStrategy: "JWT with JWKS"
pageType: use-case
---

# Add JWT Authentication to Your Cowboy API

Secure your Cowboy API using JWT authentication with JWKS.

## How Zuplo Handles It

Let Zuplo issue short-lived JWTs signed with a JWKS your Cowboy backend can verify — no long-lived API keys touch your origin.

## Cowboy Backend Code

```erlang
-module(jwt_middleware).
-export([init/2, handle/2, terminate/3]).

-include_lib("cowboy/include/cowboy.hrl").

init(Options, Req) ->
    {ok, Req, Options}.

handle(Req, State) ->
    case cowboy_req:parse_header(authorization, Req) of
        {ok, {{bearer, Token}, _}} ->
            verify_jwt(Token, Req, State);
        _ ->
            {ok, Req1} = cowboy_req:reply(401, #{<<"content-type">> => <<"application/json">>}, <<"No token provided">>, Req),
            {stop, Req1, State}
    end.

verify_jwt(Token, Req, State) ->
    Issuer = <<"https://my-api-a32f34.zuplo.api/__zuplo/issuer">>,
    JWKSUri = Issuer ++ <<"/.well-known/jwks.json">>,

    % Fetch JWKS keys (you may want to cache them)
    {ok, {_, _, Body}} = httpc:request(get, {JWKSUri, []}, [], []),
    {jose_jwk, _Keys} = jsx:decode(Body),

    % Parse and verify the JWT
    case jwt:verify(Token, _Keys, [{issuer, Issuer}, {alg, <<"RS256">>}]) of
        {ok, {jwt, Claims}} ->
            % Successfully decoded and verified
            Req1 = cowboy_req:set_binding(user, Claims, Req),
            {ok, Req1, State};
        {error, Reason} ->
            {ok, Req1} = cowboy_req:reply(401, #{<<"content-type">> => <<"application/json">>}, jsx:encode(#{error => <<"Invalid token">>, details => Reason}), Req),
            {stop, Req1, State}
    end.

terminate(_Reason, _Req, _State) -> ok.

% Example route protected by the middleware
start() ->
    Dispatch = cowboy_router:compile([{'_', [
        {"/protected", jwt_middleware, []}
    ]}]),
    {ok, _} = cowboy:start_clear(http_listener, 100, [{port, 8080}], #{env => #{dispatch => Dispatch}}).
```

## Example Request

```bash
curl -X GET \
  'https://your-api.zuplo.dev/your-route' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

## Learn More

- [API Key Authentication on Zuplo](https://zuplo.com/docs/policies/api-key-auth-inbound)
- [JWT Authentication on Zuplo](https://zuplo.com/docs/policies/open-id-jwt-auth-inbound)
- [All use cases](https://zuplo.com/use-cases)
