All Projects → greenpau → Caddy Auth Jwt

greenpau / Caddy Auth Jwt

Licence: apache-2.0
JWT Authorization Plugin for Caddy v2

Programming Languages

go
31211 projects - #10 most used programming language

Projects that are alternatives of or similar to Caddy Auth Jwt

caddy-authorize
Authorization Plugin for Caddy v2 (JWT/PASETO)
Stars: ✭ 235 (+85.04%)
Mutual labels:  authorization, caddy, rbac, jwt-authentication
Caddy Authz
Caddy-authz is a middleware for Caddy that blocks or allows requests based on access control policies.
Stars: ✭ 221 (+74.02%)
Mutual labels:  authorization, rbac, caddy
Express Mongodb Rest Api Boilerplate
A boilerplate for Node.js apps / Rest API / Authentication from scratch - express, mongodb (mongoose).
Stars: ✭ 153 (+20.47%)
Mutual labels:  jwt, authorization, jwt-authentication
React Login
A client side implementation of authentication using react.js for my blog on medium. This is the second part of my previous blog on how to implement scalable node.js server.
Stars: ✭ 105 (-17.32%)
Mutual labels:  jwt, jwt-authentication
Barong
Barong auth server
Stars: ✭ 100 (-21.26%)
Mutual labels:  jwt, jwt-authentication
Express Jwt
An example API for creating/verifying json web tokens
Stars: ✭ 105 (-17.32%)
Mutual labels:  jwt, authorization
Jwt To Rbac
JWT-to-RBAC lets you automatically generate RBAC resources based on JWT tokens
Stars: ✭ 89 (-29.92%)
Mutual labels:  jwt, rbac
Sample Spring Oauth2 Microservices
some examples that show basic and more advanced implementations of oauth2 authorization mechanism in spring-cloud microservices environment
Stars: ✭ 109 (-14.17%)
Mutual labels:  jwt, authorization
Caddy Jwt
JWT middleware for the Caddy server
Stars: ✭ 107 (-15.75%)
Mutual labels:  jwt, caddy
Spring Webmvc Pac4j
Security library for Spring Web MVC: OAuth, CAS, SAML, OpenID Connect, LDAP, JWT...
Stars: ✭ 110 (-13.39%)
Mutual labels:  jwt, authorization
Node Casbin
An authorization library that supports access control models like ACL, RBAC, ABAC in Node.js and Browser
Stars: ✭ 1,757 (+1283.46%)
Mutual labels:  authorization, rbac
Micro Jwt Auth
jwt authorization wrapper for https://github.com/zeit/micro
Stars: ✭ 97 (-23.62%)
Mutual labels:  jwt, authorization
Jcasbin
An authorization library that supports access control models like ACL, RBAC, ABAC in Java
Stars: ✭ 1,335 (+951.18%)
Mutual labels:  authorization, rbac
Netcoreblockly
.NET Core API to Blockly - generate from WebAPI, Swagger, OData, GraphQL =>
Stars: ✭ 121 (-4.72%)
Mutual labels:  jwt, jwt-authentication
Spring Security React Ant Design Polls App
Full Stack Polls App built using Spring Boot, Spring Security, JWT, React, and Ant Design
Stars: ✭ 1,336 (+951.97%)
Mutual labels:  jwt, authorization
Gin Web
由gin + gorm + jwt + casbin组合实现的RBAC权限管理脚手架Golang版, 搭建完成即可快速、高效投入业务开发
Stars: ✭ 107 (-15.75%)
Mutual labels:  jwt, rbac
Casbin Cpp
An authorization library that supports access control models like ACL, RBAC, ABAC in C/C++
Stars: ✭ 113 (-11.02%)
Mutual labels:  authorization, rbac
Jwt
Jwt.Net, a JWT (JSON Web Token) implementation for .NET
Stars: ✭ 1,694 (+1233.86%)
Mutual labels:  jwt, authorization
Vue Expenses
A simple expense tracking application
Stars: ✭ 117 (-7.87%)
Mutual labels:  jwt, jwt-authentication
Spring Boot Oauth2 Jwt Swagger Ui
Spring Boot , OAuth 2 , JWT (Json Web Token) and Swagger UI
Stars: ✭ 77 (-39.37%)
Mutual labels:  authorization, jwt-authentication

caddy-auth-jwt

JWT Authorization Plugin for Caddy v2.

Please see other relevant plugins:

Please show your appreciation for this work and ⭐️ ⭐️ ⭐️

This work is inspired by BTBurke/caddy-jwt. Many thanks to @BTBurke and other contributors to the plugin.

Please ask questions either here or via LinkedIn. I am happy to help you! @greenpau.

Table of Contents

Ask Questions

Please ask questions and I will help you!

Overview

With Caddy v2 modules (aka plugins), there is a shift in how one builds a plugin. If a plugin is being used in multiple parts of a configuration, e.g. in different routes, each part of the configuration initializes (provisions and validates) a new instance of the plugin.

For example, this authorization plugin may be used to protect multiple routes. It means that each of the routes will get its own instance of the plugin.

How does configuration in one part affects other parts?

  • By default, a single instance of a plugin inherits "default" context.
  • All instances of the plugin in an authorization context (e.g. "default" authorization context) inherit settings from the primary instance in the authorization context.
  • There is only one primary instance in an authorization context.
  • A plugin MUST have a primary instance in an authorization context.
  • If an instance is not a primary instance, and a particular configuration property is not being set, then the instance inherits the property from the primary instance.

What happens when a plugin does not have access list

  • If an instance of a plugin does not have an access list, it inherits the configuration from the primary instance in its authorization context.
  • If a primary instance does not have an access list, the instances without an access list allow access for the holders of anonymous and guest claims.

Limitations

Currently, the plugin implements limited set of features. As such the following is still under development:

  • strip_token
  • pass_claims
  • token_types: HS and RS algos are supported at the moment

⬆️ Back to Top

Plugin Users

Getting Started

JSON Configuration

This repository contains a sample configuration (see assets/conf/config.json).

My application is a reverse proxy for Prometheus and Alertmanager instances. I want to allow access to the instances to the holders of anonymous and guest claims.

The Alertmanager route is as follows. The instance of the plugin is NOT a primary instance. The configuration is only an access list.

Since the context is not specified, this instance is in "default" authorization context.

            {
              "handle": [
                {
                  "handler": "authentication",
                  "providers": {
                    "jwt": {
                      "access_list": [
                        {
                          "action": "allow",
                          "claim": "roles",
                          "values": [
                            "anonymous",
                            "guest",
                            "admin"
                          ]
                        }
                      ]
                    }
                  }
                },
                {
                  "body": "alertmanager",
                  "handler": "static_response",
                  "status_code": 200
                }
              ],
              "match": [
                {
                  "path": [
                    "/alertmanager"
                  ]
                }
              ],
              "terminal": true
            },

Next, notice that Prometheus route the the primary in its authorization context. It has the default setting for the context.

            {
              "handle": [
                {
                  "handler": "authentication",
                  "providers": {
                    "jwt": {
                      "primary": true,
                      "token_name": "access_token",
                      "token_secret": "383aca9a-1c39-4d7a-b4d8-67ba4718dd3f",
                      "auth_url_path": "/auth",
                      "access_list": [
                        {
                          "action": "allow",
                          "claim": "roles",
                          "values": [
                            "anonymous",
                            "guest",
                            "admin"
                          ]
                        }
                      ],
                      "strip_token": false,
                      "pass_claims": false,
                      "token_types": [
                        "HS256",
                        "HS384",
                        "HS512"
                      ],
                      "token_sources": [
                        "header",
                        "cookie",
                        "query"
                      ]
                    }
                  }
                },
                {
                  "body": "prometheus",
                  "handler": "static_response",
                  "status_code": 200
                }
              ],
              "match": [
                {
                  "path": [
                    "/prometheus"
                  ]
                }
              ],
              "terminal": true
            },

The primary indicates that the instance is the primary instance in its authorization context.

The token_sources configures where the plugin looks for an authorization token. By default, it looks in Authorization header, cookies, and query parameters.

The following Caddyfile directive instructs the plugin to search for Authorization: Bearer <JWT_TOKEN> header and authorize the found token:

    jwt {
      option validate_bearer_header
    }

Test it with the following curl command:

curl --insecure -H "Authorization: Bearer JWT_TOKEN" -v https://localhost:8443/myapp

The token_name indicates the name of the token in the token_sources. By default, it allows jwt_access_token and access_token.

The token_secret is the password for symmetric algorithms. If the secret is not provided in the configuration, it can be passed via environment variable JWT_TOKEN_SECRET.

The auth_url_path is the URL a user gets redirected to when a token is invalid.

The access_list is the series of entries defining how to authorize claims. In the above example, the plugin authorizes access for the holders of "roles" claim where values are any of the following: "anonymous", "guest", "admin".

⬆️ Back to Top

Caddyfile

The following Caddyfile configuration mirrors closely the above JSON configuration.

{
  http_port 8080
  https_port 8443
  debug
}

localhost:8443 {
  route /prometheus* {
    jwt {
      primary yes
      trusted_tokens {
        static_secret {
          token_name access_token
          token_secret 383aca9a-1c39-4d7a-b4d8-67ba4718dd3f
        }
      }
      auth_url /auth
      allow roles anonymous guest admin
    }
    respond * "prometheus" 200
  }

  route /alertmanager* {
    jwt
    respond * "alertmanager" 200
  }

  route /auth* {
    respond * "auth portal" 200
  }

  route /version* {
    respond * "1.0.0" 200
  }

  route {
    redir https://{hostport}/auth 302
  }
}

Please note that the jwt directive instucts the instance of the plugin to inherit all of its properties from the primary instance. This greatly simplifies the configuration.

route /alertmanager* {
  jwt
  respond * "alertmanager" 200
}

⬆️ Back to Top

Verification with RSA Public Keys

The following Caddyfile configuration has two different trusted token backends:

  • static_secret: based on shared secret, i.e. cdcdc37a-6c65-4e43-b48a-8d047643d9df
  • public_key: validates key ID Hz789bc303f0db with the RSA Public Key in /etc/gatekeeper/auth/jwt/verify_key.pem
  route /prometheus* {
    jwt {
      primary yes
      trusted_tokens {
        static_secret {
          token_name access_token
          token_secret cdcdc37a-6c65-4e43-b48a-8d047643d9df
        }
        public_key {
          token_name access_token
          token_rsa_file Hz789bc303f0db /etc/gatekeeper/auth/jwt/verify_key.pem
        }
      }

The verify_key.pem is generated with the following command:

openssl genrsa -out /etc/gatekeeper/auth/jwt/sign_key.pem 2048
openssl rsa -in /etc/gatekeeper/auth/jwt/sign_key.pem -pubout -out /etc/gatekeeper/auth/jwt/verify_key.pem

The content of verify_key.pem follows:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAphJPa8M0D/iY/I6kAs7K
4M30kPfurFEwpJe4zd9h9E/iuWbqpHCx+sQqAG8xJawddG6WupZiWRY3+44hw7nH
srH7XY2Dv/6igo1WU6U0PjHQ0SRSKGkGb3x4iwHx8IMsUQ44iDZYugxrjf5xkthc
6MNwqqcTuHLJtgEqSPETiqZgbcRHEWtqPb/LuQl3hLscokO7e5Yw0LQibtnZt4UR
Wb3z9CrzP8yS2Ibf8vbhiVhzYWSkXOiwsA0X5sBdNZbg8AkkqgyVe2FtCPBPdW6/
KOj8geX+P2Wms6msOZIRk7FqpKfEiK//arjumEsVF34S7GPavynLmyLfC4j9DcFI
PQIDAQAB
-----END PUBLIC KEY-----

⬆️ Back to Top

Auto-Redirect URL

Consider the following configuration snippet. When the JWT plugin detects unauthenticated user, it forwards the user to https://auth.example.com.

https://chat.example.com {
  jwt {
    auth_url https://auth.example.com/auth
  }
}

By default, the plugin adds the redirect_url parameter in URL query pointing back to the page where the plugin detected unauthenticated user. It signals an authenticator to redirect where to redirect the user upon successful authentication.

If you would like to disable the addition of redirect_url, please add disable auth_redirect_query:

https://chat.example.com {
  jwt {
    auth_url https://auth.example.com/auth
    disable auth_redirect_query
  }
}

If jwt configuration contains the following directive, then the redirect is disabled and the request is refused with a HTTP 401 Unauthorized error.

jwt {
  disable auth_redirect
}

⬆️ Back to Top

Plugin Developers

This section of the documentation targets a plugin developer who wants to issue JWT tokens as part of their plugin.

Please see caddy-auth-portal for an example how to issue JWT tokens.

First, a developer would need to create TokenProviderConfig object via NewTokenProviderConfig().

tokenProvider := jwt.NewTokenProviderConfig()

Second, set the TokenProviderConfig properties, e.g.:

  • TokenName
  • TokenOrigin
  • TokenLifetime

Next, create a claim:

    claims := &jwt.UserClaims{}
    claims.Subject = username
    claims.Email = username
    claims.Name = "Smith, John"
    claims.Roles = append(claims.Roles, "anonymous")
    claims.Roles = append(claims.Roles, "guest")
    claims.Origin = tokenProvider.TokenOrigin
    claims.ExpiresAt = time.Now().Add(time.Duration(tokenProvider.TokenLifetime) * time.Second).Unix()

Finally, having created claims, the developer can create a token string:

userToken, err := claims.GetToken("HS512", []byte(m.TokenProvider.TokenSecret))

⬆️ Back to Top

Role-based Access Control and Access Lists

Sources of Role Information

By default, the plugin finds role information in the following token fields:

  • roles
  • role
  • group
  • groups
  • app_metadata - authorization - roles
  • realm_access - roles

In the below example, the use has a single role, i.e. anonymous.

{
  "exp": 1596031874,
  "sub": "jsmith",
  "name": "Smith, John",
  "email": "[email protected]",
  "roles": [
    "anonymous"
  ],
  "origin": "localhost"
}

Additionally, the token validation component of the plugin recognized that roles may be in other parts of a token, e.g. app_metadata - authorization - roles:

{
  "app_metadata": {
    "authorization": {
      "roles": ["admin", "editor"]
    }
  }
}

Additionally, realm_access - roles:

{
  "realm_access": {
    "roles": ["admin", "editor"]
  }
}

References:

Anonymous Role

By default, if the plugin does not find role information in JWT token, then automatically treats the token having the following two roles:

  • anonymous
  • guest

For example, it happens when:

  • roles and app_metadata are not present in a token
  • app_metadata does not contain authorization

⬆️ Back to Top

Granting Access with Access Lists

The authorization in the context of Caddy v2 is being processed by an authentication handler, e.g. this plugin. The following snippet is a configuration of one instance of the plugin (handler).

{
  "handler": "authentication",
  "providers": {
    "jwt": {
      "access_list": [
        {
          "action": "allow",
          "claim": "roles",
          "values": [
            "anonymous",
            "guest",
            "admin"
          ]
        }
      ]
    }
  }
}

The access_list data structure contains a list of entries.

Each of the entries must have the following fields:

  • action: allow or deny
  • claim: currently the allowed values are roles, scopes, and audience. The future plan for this field is the introduction of regular expressions to match various token fields
  • value: it could be the name of a role, scope or audience, or * or any for any value. The future plan for this field is the introduction of regular expressions to match claim names

By default, if a plugin instance is primary and access_list key does not exist in its configuration, the instance creates a default "allow" entry. The entry grants access to anonymous and guest roles.

If there an entry with a matching claim and the action associated with the entry is deny, then the claim is not allowed. This deny takes precedence over any other matching allow.

The "catch-all" action is deny.

⬆️ Back to Top

Default Allow ACL

If jwt configuration contains the following directive, then The "catch-all" action is allow.

jwt {
  default allow
}

⬆️ Back to Top

Multiple Allow or Deny Directives

If jwt configuration contains multiple allow or deny directives, they are processed as follows:

  • Any matching allow will pass the authorization request
  • Any matching deny will fail the authorization request

For example, the following configuration allows JWT token holders of roles anonymous or guest, or of audience https://localhost/ or https://example.com/.

jwt {
  allow roles anonymous guest
  allow audience https://localhost/ https://example.com/
}

The validate allow_all directive overrides this default behavior, and validate allow_any restores it.

For example, the following configuration requires that JWT token holders have either anonymous or guest role, and either https://localhost/ or https://example.com/ audience.

jwt {
  allow roles anonymous guest
  allow audience https://localhost/ https://example.com/
  validate allow_all
}

HTTP Method and Path in ACLs

The jwt plugin allows specifying HTTP method and path in access lists.

For example, the following configuration allows JWT token holders of roles anonymous or guest to access the route, except for:

  • POST to any endpoint
  • GET to /internal/dashboard endpoint
route /* {
  jwt {
    deny roles anonymous guest with method get /internal/dashboard
    deny roles anonymous guest with method post
    allow roles anonymous guest
  }
  respond * "OK" 200
}

⬆️ Back to Top

Forbidden Access

By default, caddyauth.Authenticator plugins should not set header or payload of the response. However, caddy, by default, responds with 401 (instead of 403), because caddyauth.Authenticator does not distinguish between authorization (403) and authentication (401).

The plugin's default behaviour is responding with 403 Forbidden.

However, one could use the forbidden Caddyfile directive to redirect users to a custom 403 page.

jwt {
  # forbidden <path>
  forbidden /custom_403.html
}

⬆️ Back to Top

Path-Based Access Lists

There are application that specify ACL in its own body, e.g.

{
  "iat": 1532093588,
  "jti": "705b6f50-8c21-11e8-9bcb-595326422d60",
  "sub": "jamie",
  "exp": "1532179987",
  "role": "users",
  "acl": {
    "paths": {
      "/*/users/**": {},
      "/*/conversations/**": {},
      "/*/sessions/**": {},
      "/*/devices/**": {},
      "/*/image/**": {},
      "/*/media/**": {},
      "/*/applications/**": {},
      "/*/push/**": {},
      "/*/knocking/**": {}
    }
  },
  "application_id": "aaaaaaaa-bbbb-cccc-dddd-0123456789ab"
}

To enable the validation of whether the requested path matches one of the paths in JWT token claims, use the following Caddyfile directive:

jwt {
   validate acl_path
}

The asterisk * signs get converted to the following regex patterns:

  • *: [a-zA-Z0-9_.~-]+
  • **: [a-zA-Z0-9_/.~-]+

Pass Token Claims in HTTP Headers

To pass JWT token claims in HTTP headers to downstream plugins, use the following Caddyfile directive:

jwt {
   ...
   enable claim headers
   ...
}

The downstream plugins would get the following X-Token- headers:

    "X-Token-Subject": "webadmin"
    "X-Token-User-Name": "Web Administrator"
    "X-Token-User-Email": "[email protected]"
    "X-Token-User-Roles": "superadmin guest anonymous"

⬆️ Back to Top

Caddyfile Shortcuts

The following snippet in jwt Caddyfile:

    jwt {
      trusted_public_key 1 /etc/caddy/auth/jwt/jwt_publickey.pem
      ...
    }

Replaces the following:

    jwt {
      trusted_tokens {
        public_key {
          token_rsa_file 1 /etc/caddy/auth/jwt/jwt_publickey.pem
        }
      }
      ...
    }

⬆️ Back to Top

User Identity

When the plugin successfully validates a JWT token, the plugin passes the user identity identifier back to the Caddy server.

By default, the identity passed to Caddy is email address. However, it could be changed with user_identity Caddyfile directive.

    jwt {
      user_identity id
      user_identity subject
      user_identity email
      ...
    }

If email is being set, but a JWT token does not contain an email address, then the plugin uses subject for identity.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].