Giter Site home page Giter Site logo

caddy-jwt's Introduction

JWT

Build Status

Note on Caddy v2

There is a major new version of Caddy in beta. Caddy v2 makes significant changes to how plugins work, to the point that this plugin will no longer work with the latest version of caddy and would require significant effort to update it. If you use this plugin and would like a Caddy v2 version, please let me know your wish list of features on this issue.

Authorization Middleware for Caddy

This middleware implements an authorization layer for Caddy based on JSON Web Tokens (JWT). You can learn more about using JWT in your application at jwt.io.

Basic Syntax

jwt [path]

By default every resource under path will be secured using JWT validation. To specify a list of resources that need to be secured, use multiple declarations:

jwt [path1]
jwt [path2]

Important You must set the secret used to construct your token in an environment variable named JWT_SECRET(HMAC) or JWT_PUBLIC_KEY(RSA or ECDSA). Otherwise, your tokens will silently fail validation. Caddy will start without this value set, but it must be present at the time of the request for the signature to be validated.

Advanced Syntax

You can optionally use claim information to further control access to your routes. In a jwt block you can specify rules to allow or deny access based on the value of a claim. If the claim is a json array of strings, the allow and deny directives will check if the array contains the specified string value. An allow or deny rule will be valid if any value in the array is a match.

jwt {
   path [path]
   redirect [location]
   allow [claim] [value]
   deny [claim] [value]
}

To authorize access based on a claim, use the allow syntax. To deny access, use the deny keyword. You can use multiple keywords to achieve complex access rules. If any allow access rule returns true, access will be allowed. If a deny rule is true, access will be denied. Deny rules will allow any other value for that claim.

For example, suppose you have a token with user: someone and role: member. If you have the following access block:

jwt {
   path /protected
   deny role member
   allow user someone
}

The middleware will deny everyone with role: member but will allow the specific user named someone. A different user with a role: admin or role: foo would be allowed because the deny rule will allow anyone that doesn't have role member.

If the optional redirect is set, the middleware will send a redirect to the supplied location (HTTP 303) instead of an access denied code, if the access is denied.

Ways of passing a token for validation

There are three ways to pass the token for validation: (1) in the Authorization header, (2) as a cookie, and (3) as a URL query parameter. The middleware will by default look in those places in the order listed and return 401 if it can't find any token.

Method Format
Authorization Header Authorization: Bearer <token>
Cookie "jwt_token": <token>
URL Query Parameter /protected?token=<token>

It is possible to customize what token sources should be used via the token_source rule. If at one or more token_source rules are specified, they will be used instead of the default in the given order. For example, to do the same validation as default, but with the different header, cookie and query param names, the user could use the following snippet:

jwt {
   ...
   token_source header my_header_type
   token_source cookie my_cookie_name
   token_source query_param my_param_name
}

Note about Authorization header

The optional header type is for Bearer tokens by other names, like ApplePass used by Apple PassKit. If not provided, it defaults to Bearer as defined in RFC6750. This does not support more complex schemes that require a challenge/response.

Constructing a valid token

JWTs consist of three parts: header, claims, and signature. To properly construct a JWT, it's recommended that you use a JWT library appropriate for your language. At a minimum, this authorization middleware expects the following fields to be present:

Header
{
"typ": "JWT",
"alg": "HS256|HS384|HS512|RS256|RS384|RS512|ES256|ES384|ES512"
}
Claims

If you want to limit the validity of your tokens to a certain time period, use the "exp" field to declare the expiry time of your token. This time should be a Unix timestamp in integer format.

{
"exp": 1460192076
}

Acting on claims in the token

You can of course add extra claims in the claim section. Once the token is validated, the claims you include will be passed as headers to a downstream resource. Since the token has been validated by Caddy, you can be assured that these headers represent valid claims from your token. For example, if you include the following claims in your token:

{
  "user": "test",
  "role": "admin",
  "logins": 10,
  "groups": ["user", "operator"],
  "data": {
    "payload": "something"
  }
}

The following headers will be added to the request that is proxied to your application:

Token-Claim-User: test
Token-Claim-Role: admin
Token-Claim-Logins: 10
Token-Claim-Groups: user,operator
Token-Claim-Data.payload: something

Token claims will always be converted to a string. If you expect your claim to be another type, remember to convert it back before you use it. Nested JSON objects will be flattened. In the example above, you can see that the nested payload field is flattened to data.payload.

All request headers with the prefix Token-Claim- are stripped from the request before being forwarded upstream, so users can't spoof them.

Claims with special characters that aren't allowed in HTTP headers will be URL escaped. For example, Auth0 requires that claims be namespaced with the full URL such as

{
  "http://example.com/user": "test"
}

The URL escaping will lead to some ugly headers like

Token-Claim-Http:%2F%2Fexample.com%2Fuser: test

If you only care about the last section of the path, you can use the strip_header directive to strip everything before the last portion of the path.

jwt {
  path /
  strip_header
}

When combined with the claims above, it will result in a header:

Token-Claim-User: test

Allowing Public Access to Certain Paths

In some cases, you may want to allow public access to a particular path without a valid token. For example, you may want to protect all your routes except access to the /login path. You can do that with the except directive.

jwt {
  path /
  except /login
}

Every path that begins with /login will be excepted from the JWT token requirement. All other paths will be protected. In the case that you set your path to the root as in the example above, you also might want to allow access to the so-called naked or root domain while protecting everything else. You can use the directive allowroot which will allow access to the naked domain. For example, if you have the following config block:

jwt {
  path /
  except /login
  allowroot
}

Requests to https://example.com/login and https://example.com/ will both be allowed without a valid token. Any other path will require a valid token.

Allowing Public Access Regardless of Token

In some cases, a page should be accessible whether a valid token is present or not. An example might be the Github home page or a public repository, which should be visible even to logged-out users. In those cases, you would want to parse any valid token that might be present and pass the claims through to the application, leaving it to the application to decide whether the user has access. You can use the directive passthrough for this:

jwt {
  path /
  passthrough
}

It should be noted that passthrough will always allow access on the path provided, regardless of whether a token is present or valid, and regardless of allow/deny directives. The application would be responsible for acting on the parsed claims.

Specifying Keys for Use in Validating Tokens

There are two ways to specify key material used in validating tokens. If you run Caddy in a container or via an init system like Systemd, you can directly specify your keys using the environment variables JWT_SECRET for HMAC or JWT_PUBLIC_KEY for RSA or ECDSA (PEM-encoded public key). You cannot use both at the same time because it would open up a known security hole in the JWT specification. When you run multiple sites, all would have to use the same keys to validate tokens.

When you run multiple sites from one Caddyfile, you can specify the location of a file that contains your PEM-encoded public key or your HMAC secret. Once again, you cannot use both for the same site because it would cause a security hole. However, you can use different methods on different sites because the configurations are independent.

For RSA or ECDSA tokens:

jwt {
  path /
  publickey /path/to/key.pem
} 

For HMAC:

jwt {
  path /
  secret /path/to/secret.txt
}

When you store your key material in a file, this middleware will cache the result and use the modification time on the file to determine if the secret has changed since the last request. This should allow you to rotate your keys or invalidate tokens by writing a new key to the file without worrying about possible file locking problems (although you should still check that your write succeeded before issuing tokens with your new key.)

If you have multiple public keys or secrets that should be considered valid, use multiple declarations to the keys or secrets in different files. Authorization will be allowed if any of the keys validate the token.

jwt {
  path /
  publickey /path/to/key1.pem
  publickey /path/to/key2.pem
}

Possible Return Status Codes

Code Reason
401 Unauthorized - no token, token failed validation, token is expired
403 Forbidden - Token is valid but denied because of an ALLOW or DENY rule
303 A 401 or 403 was returned and the redirect is enabled. This takes precedence over a 401 or 403 status.

Caveats

JWT validation depends only on validating the correct signature and that the token is unexpired. You can also set the nbf field to prevent validation before a certain timestamp. Other fields in the specification, such as aud, iss, sub, iat, and jti will not affect the validation step.

caddy-jwt's People

Contributors

btburke avatar hairyhenderson avatar icb- avatar igor-petruk avatar lsjostro avatar magikstm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

caddy-jwt's Issues

JWT custom-claims as var

So i want get a custom claims as var and proxy that requests.
It's possible with your plugin?

Supporting RSA-SHA256

Wonderful middleware! It would be even better with public key algorithm support. I can give it a shot and send a PR in a week or two.

Caddy's import path has changed

Caddy's import path (and Go module name) has changed from

github.com/mholt/caddy

to

github.com/caddyserver/caddy

Unfortunately, Go modules are not yet mature enough to handle a change like this (see https://golang.org/issue/26904 - "haven't implemented that part yet" but high on priority list for Go 1.14) which caught me off-guard. Using Go module's replace feature didn't act the way I expected, either. Caddy now fails to build with plugins until they update their import paths.

I've hacked a fix into the build server, so downloading Caddy with your plugin from our website should continue working without any changes on your part, for now. However, please take a moment and update your import paths, and do a new deploy on the website, because the workaround involves ignoring module checksums and performing a delicate recursive search-and-replace.

I'm terribly sorry about this. I did a number of tests and dry-runs to ensure the change would be smooth, but apparently some unknown combination of GOPATH, Go modules' lack of maturity, and other hidden variables in the system or environment must have covered up something I missed.

This bash script should make it easy (run it from your project's top-level directory):

find . -name '*.go' | while read -r f; do
	sed -i.bak 's/\/mholt\/caddy/\/caddyserver\/caddy/g' $f && rm $f.bak
done

We use this script in the build server as part of the temporary workaround.

Let me know if you have any questions! Sorry again for the inconvenience.

Do not see Claims passed as headers in request

I have defined proxy in Caddy file which passed all the rest api request to the proxied server.

I have added extra claims like id ("abc") and type ("xyz") and passing jwt_token in cookie which is HttpOnly. When the control comes in the api method and if I do r.Header.Get("Token-Claim-Id") or r.Header.Get("Token-Claim-Type") I see its blank. Even r.Header.Get("Token") is returning blank.

Note: Cookie is having jwt_token and the value is also there.

Please let me know what is that I an missing or my understanding is wrong.

Thanks,
Juned S

Optionally secure paths based on claims

Allow a path to be secured based on the value of a validated claim.

Proposed Caddyfile format:

To authorize a path only for a certain claim value:

jwt [path] {
   allow <claim> <value>
}

To authorize any validated token except for a certain claim value:

jwt [path] {
   deny <claim> <value>
}

For example, to authorize a path only for a token with

{
"role": "admin"
}
jwt /protected_admin_stuff {
    allow role admin
}

Advice on secret file format for HMAC

Hi Bryan. Not an issue in your code, so much as a daft question from a Caddy/JWT/Linux newbie! Apologies in advance but I have struggled to find any detailed docs on how to declare JWT_SECRET in my secret file.

If my secret was "opensesame", is the contents of my file simply...

JWT_SECRET='opensesame'

I suspect not because I can't get my setup to work.

Question about releases

This is not an issue, just a question so I can understand Go better.

In the Releases the most recent release is 3.7.2.

If I do
go get github.com/BTBurke/caddy-jwt
Go gets v3.7.1.

[email protected]+incompatible

Why is that?

Protect an exact path

I would like the ability to protect an exact path, and not the child directory. The reason is that I'd want to protect the file listing (Served with browse on the / path) but allow access to all subfiles. This is for a download server where the paths should stay private.

Essentially, I'd lime a config similar to:

https://example.com {
  root /srv/http

  jwt {
     path /
     exact_path true
     redirect /login
  }

  login {
    login_path /login
  }

  browse
}

Anyone would be able to access the paths under /, but getting to / (invoking browse) would require a login.

Refactor

New features added over the past 6 months have created some really bloated files and complicated architecture. The plugin needs a major refactor to break up major functionality and improve code documentation. Way too much stuff is in the ServeHTTP handler that could be separated out into smaller functions for independent testing.

The list of items below are some ideas for a major refactoring. If you're looking for some way to contribute to this plugin, feel free to tackle any of these.

  • Get rid of the global backend cache - Currently, there is a global cache for key material that is read from the filesystem. It contains some state in backend.current that is the source of subtle bugs, particularly during testing. The backend should be broken out into its own file and become the main interface for dealing with secrets, whether in files, env vars, or elsewhere (kubernetes secrets?). Ideally, the current state should be removed and passed explicitly in calls to locate key material. The backend should move into the Auth struct because there's no good reason to keep it as a global var and would make it easier to mock in tests.

  • Deprecate individual claims in headers - Claims from a valid token are passed as individual HTTP headers. This works fine for a fairly flat and small claim structure. Some third-party tokens (like Auth0) bundle a lot of deeply nested and FQDN-namespaced claims that cause problems with HTTP headers. They can contain non-ASCII characters. Currently, the claim names are URL-encoded but the values are not. This can cause subtle bugs and errors when non-ASCII characters are encoded either as header names or values. Over time, I would like to deprecate the individual headers in favor of sending all the claims in one base64-encoded JSON structure that can be read/manipulated downstream. This removes the requirement to flatten deeply-nested claim values, allows UTF-8 values, and eliminates problems with type-coercion to strings. This is a major breaking change.

Token-Claims: <base64 encoded JSON stucture>
  • Refactor tests - The test file test_jwt.go now clocks in at near 1000 lines due to scope-creep. Many of the tests are end-to-end integration tests because the individual features are not broken out into smaller functions. At the same time these are broken out, I'd like to refactor the tests to improve the ability to test smaller pieces of functionality. This would allow more advanced security testing techniques, like fuzzing inputs. It might be better to move away from ginkgo/gomega for tests, relying more on test tables and standard library facilities.

  • [x] Consider vendoring dependencies - At a minimum, the core dependency on jwt-go should be vendored to prevent a major upgrade of that library from breaking new Caddy builds. Dep is now stable enough to be used. The vendor directory would have to be committed to the repository because the Caddy build server cannot deal with dep lock files. This is not supported by the current build server.

  • Add optional logging - When this plugin was created, the best practice for Caddy plugins was not to log anything. I think logging should be optional, but it needs to integrate well with Caddy main logging functionality. It would be an immense help in debugging scenarios. Currently, the only information that is available is the 401 response, which doesn't provide enough information to determine the cause of the failure, which could be a result of missing secrets, misconfiguration, etc.

Response after succesful login: www-authenticate header: Bearer realm="",error="invalid_token"

Just downloaded a fresh Caddy v0.11.5 with

  • http.jwt v3.7.0
  • http.login v1.3.0

And then set it up with a caddyfile as described in the http.login docs:

jwt {
    path /
    allow sub bob
}

login / {
         simple bob=secret,alice=secret
}

When I log in with bob at /login I'm always redirected back to /login.
The response contains the following:
www-authenticate header: Bearer realm="",error="invalid_token".
I can see in Caddy's log that the authentication was successful.

Any idea what went wrong? Are the Plugin versions not compatible?

BTW the same works with

  • http.jwt v3.3.0
  • http.login v1.1.0

Didn't try the versions in between.

Validate multiple claims in an allow rule?

How amenable are you to having caddy-jwt be able to validate multiple claims? Any particular rules if I were to work on this and submit a PR?

Scenario: have set up Caddy using caddy-jwt and tarent/loginsrv where the latter can speak to multiple OAuth2 providers. I have GitHub configured, I'd like to configure an alternative to use, but at present it looks like the allow rules would mean that if I add a second provider then someone in that provider who has the same login name (sub) from one of my valid users in the first-provider would then be authorized.

So I want to be able to assert both the origin and the sub at the same time.

Is this functionality in-scope for this plugin or should I be looking at some other generic authorization plugin, layered atop caddy-jwt, instead?

Tentatively thinking that an AccessRule would have a slice of Claim, Value pairs and validation would check that the length of remaining args is zero modulo 2, instead of being exactly 2, and adjust what's stored to match the adjusted model; then amend the AccessRules evaluation in jwt.go. Sound sane?

Loading cookie with alternative name

I'm trying to use Caddy to set up SSO between some services that are proxied by Caddy on different subdomains. This works pretty well, by setting the cookie-domain of the login plugin to a common domain all these services share. However, I'd like to be able to change the cookie name, as the default name "jwt_token" seems pretty generic, which I think could easily be overwritten by a careless service. http.login supports the cookie_name directive, but as far as I can tell, there's no equivalent in caddy-jwt.

Question: Auth0 with caddy-jwt

Hi I'm a bit new to jwt, I'm trying to use Auth0 with this:

I've set an enviroment variable for the secret on my machine to the same secret on Auth0.

I've tried 'name' and 'screenname' from Auth0 token in the Caddyfile configuration to no avail.

I realise Auth0 is not specific to this library but figured it should conform and be usable?

I'm testing with the ?token=xx I get the 401 Unauthorized so I know the caddy plugin is working.

expiry time not validated?

Hi, I'm not sure if I'm doing something wrong or if this is a bug..

  1. given this Caddyfile:
localhost {
  log stdout
  jwt /protected
}
  1. invoke caddy like so: env JWT_SECRET=testing caddy
  2. visit http://localhost:2015/protected?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMDAiLCJyb2xlIjoibWVtYmVyIn0.CYkF_TZi1dBR3wbM8UtQtbTQAfy1bzXU__McQPTLm0U
  3. 404 is returned instead of an expected 401

I set the "exp" claim to 100 in the token - it can be viewed/validated at https://jwt.io/

Am I using this incorrectly? I also tried using a valid expiry time in the past, e.g. 1460046313, and still got the 404 instead of a 401.

[Feature Request] more customization options.

honestly I think That this is a very intresting plugin since it essentially does htaccess-like access structure, but with allowing custom logins and similar fun (something I searached long enough with no proper results, funnily, I fairly recently changed the format of the cookie to a JWT, and just now I found this plugin.

There just a few things that would make it nicer.

  1. specify the way of passing, including customization, for example so I could force it to a cookie and have it a name I like.
  2. specify necessary/forbidden header fields, someone might for example only use one was of signing the JWT (HS512 in my case) and reject the rest, also there may be a different type for example "auth" to discern it from other JWTs used in a web application (I think this shouldnt be TOO hard since it's essentially same as the normal allow/deny commands, but on the header instead of the payload)

[Feature Request] Allow whitelist paths

Consider a case where I want to protect all paths except for the home page (/) and the login page.

I want to do something like:

jwt {
  path /
  redirect /login
  allow user myname 
}

But this also protects the pages I want to be open. My only option is to explicitly define paths I want protected, which could be a bit of a pain. I feel like there should be a way to handle this across all directives, but I have not seen one.

What if I could do:

jwt {
  path /
  except /
  except /login
  redirect /login
  allow user myname 
}

Or something like that?

/ is always an odd case, since for except it needs to be an exact match, not a subpath. Thoughts?

Using placeholders for allow directive

Hello,

I am starting with Caddy and I am trying to use placeholders for the allow directive in the JWT config. I have following testing scenario:

(auth) {
  jwt {
    path /

    # this works
    # allow aud app1.127.0.0.1.nip.io:2015

    # this does not work
    allow aud {host}
  }
}

app1.127.0.0.1.nip.io:2015 {
  tls off
  import auth
  proxy / http://app1:8000
}

app2.127.0.0.1.nip.io:2015 {
  tls off
  import auth
  proxy / http://app2:8000
}

During my fiddling with Caddy I learnt that placeholders are not supported on every directive. Is that the reason for this not working? If so, do you see this as something that can be added to caddy-jwt?

My reason for this is that I have SSO service which I am using to issue the tokens. It is working very well, but I need to prevent JWT token reuse (taking token from app1 and using it for app2). Tokens already contain the claim which I would like to use for validation, but I need to use host name dynamically (the app config is auto-configured from docker in my scenario and I don't have much control over that).

Is the latest plugin applied from the caddy download?

Hi,
First of all, thanks for this excellent jwt plugin for caddy. I really appreciate it.
I was wondering if the latest version is applied to the official caddy site's download.
I've downloaded caddy today with jwt included and I can confirm receiving a 401 without a valid jwt but the redirect option doesn't seem to be working.
The jwt directive is as below but I still only get 401 Unauthorized page.

jwt {
  path /secret_path
  redirect /login
}

Apologies if my configuration is mistaken.
Any support would help me a lot.

Thanks in advance.

Set referer header on redirect

I’d like the referer header to be set when using the redirect option.

My specific reason is that when used in conjunction with the http.login middleware, it requires a referer host to match the redirect host after successful login by default. Since jwt doesn’t set the referer, one has to disable the referer checking in the login plugin for it to work correctly.

Take http status code as a configuration value

Hi,

We would like to be able to configure the status code, specifically instead of 303 we want to use 307

Maybe a config property RedirectCode=307 or have a ,307 after the url?

(edited to correct copy pasta typo)

JWT signed with H256 results in a 401

I cannot get the http.jwt plugin to work with a JWT signed with H256. It always results in a 401 error.

What I've tried:

  • Setting the secret in the JWT_SECRET environment variable.
  • Setting the secret as the value of the secret option.
  • Setting the secret in a file at /etc/secrets/app.txt and adding the path as the value of the secret option. I have validated that there is no newline at the end of the file with cat -vet /etc/secrets/app.txt.
  • Temporarily set the chmod of the secret file to 777.
  • Validated the signature in code using the Node library jsonwebtoken and it's valid.
  • Validated that the cookie is sent with the request.

My configuration:

<public_domain> {
  import ssl

  proxy / http://<internal_address>

  jwt {
    path /
    except /favicon.ico

    allow group Admin

    token_source cookie organizr_token_<uuid>
    secret /etc/secrets/app.txt
  }
}

At this point I really don't know what else to try. It seems that no matter what I do the request is rejected with a 401. Interestingly enough, the plugin seems to work if I pair it with the http.login plugin and Google OAuth, but not with a JWT alone.

Status code and WWW-Authenticate response header in case of failed token validation

The relevant RFC section details status code and WWW-Authenticate response header in case of token validation failure. I observed Caddy does not follow that and always return 401. Is this by design on does caddy-jwt aim at improving at handling validation failure following RFC?

I am passing the JWT by Authorization header with Bearer scheme. I know the JWT may be passed by any one of several approaches but I am focusing on the Authorization header as it is the one for which I analyzed the IETF RFCs.

I am using Caddy 0.9.5 with JWT plugin as downloaded from https://caddyserver.com/download/builds/173003033949962/caddy_linux_amd64_custom.tar.gz

jwt { ... } block just for specifying lists of paths

Just going through the docs, and was thinking, that instead of doing:

jwt {
    path /path1
    path /path2
    path /path3
}

It might just be easier to do:

jwt /path1
jwt /path2
jwt /path3

Are these equivalent? Can we drop the first syntax in favor of the second one to keep things simpler? (If so, feel free to just update this repo; I will update the docs.)

'Contains' for check against lists

I plan to support a list of groups, which a user can have in loginsrv.
It would be cool to also have support for that in caddy-jwt. I could think of a syntax like:

allow claim > value
deny claim > value

Where claim may be an json array of strings.

What do you think about that?
Should I implement this and do a PR?
Any objections or better ideas about the syntax?

allow access for groups

JWT token has the following claims:

{
  "name": "Greenberg, Paul",
  "groups": [
    "AzureAD_Administrator",
    "AzureAD_Editor"
  ]
}

What is the way to allow access to a page for anyone in AzureAD_Administrator group?

Error 403 when using filemanager with jwt.

Hi there,

I recently tried to setup caddy with filemanager, protected by jwt. However every time I try to edit, delete or upload a file the xhr request fails with a 403 code.

What works

With jwt: Reading files and directories.
With basicauth or no auth at all: Everything.

What doesn't work

With jwt: Uploading, editing, renaming and deleting files as well as adding directories.

My setup

Caddy version: Caddy 0.9.1 (+e8e5595 Thu Aug 18 07:29:18 UTC 2016)
Caddyfile:

files.MYDOMAIN {
    root /var/www/MYDOMAIN/files
    import config/php7.conf # This includes a fastcgi directive for php7 (this works)
    jwt {
        path /MYNAME
        allow user MYNAME
    }
    filemanager {
        on /MYNAME/
        show /var/www/MYDOMAIN/files/MYNAME
    }
}

Parts of my php script for logging in:

<?php

require 'vendor/autoload.php';
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha256;

$jwt_secret = 'I wont show this but it also is exported to the JWT_SECRET environment variable. ;)';
$expiration_time = 3600;

$users = json_decode(file_get_contents('users.json'), true);

$username = array_key_exists('username', $_POST) ? $_POST['username'] : null;
if($username !== null) {
    $user = array_key_exists($username, $users) ? $users[$username] : null;
    $pwd = $_POST['password'];
    if($user !== null && password_verify($pwd, $user['pwd'])) {
        $hmac = new Sha256();
        $token = (new Builder())
                        ->setIssuer('https://files.MYDOMAIN')
                        ->setAudience('https://files.MYDOMAIN')
                        ->setIssuedAt(time())
                        ->setNotBefore(time())
                        ->setExpiration(time() + $expiration_time)
                        ->set('user', $username)
                        ->set('role', $user['role'])
                        ->sign($hmac, $jwt_secret)
                        ->getToken();

        setcookie('jwt_token', $token, time()+$expiration_time, '/', 'files.MYDOMAIN', true);
        header("Location: https://files.MYDOMAIN/".$username);
        $result = "success";
    } else {
        $result = "failure";
    }
} else {
    $result = "";
}

Here is a request for creating a folder called "test" using google chrome:

Request URL:https://files.MYDOMAIN/MYNAME/
Request Method:POST
Status Code:403 
Remote Address:MYIP:443

Response Headers
content-length:14
content-type:text/plain; charset=utf-8
date:Sun, 21 Aug 2016 19:05:46 GMT
server:Caddy
status:403
x-content-type-options:nosniff

Request Headers
:authority:files.MYDOMAIN
:method:POST
:path:/MYNAME/
:scheme:https
accept:*/*
accept-encoding:gzip, deflate, br
accept-language:de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
cache-control:no-cache
content-length:0
cookie:view-list=false; jwt_token=VALID-JWT-TOKEN
dnt:1
filename:test
origin:https://files.MYDOMAIN
pragma:no-cache
referer:https://files.MYDOMAIN/MYNAME/
token:SOME-TOKEN-FROM-FILEMANAGER
user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36

What I tried so far

I opened a post on the caddy forum in case it was just a mistake in my config. There @matt pointed me to the issue hacdias/caddy-filemanager#17 hinting at filemanager providing the css and js files on the fly and not via the filesystem.

I also made a quick site protected by jwt which just sends of an xhr request to see if it is a general problem with jwt and xhr but that worked.

I am happy to supply more information and test things as needed.

secret file for jwt authentication doesn't work

I use HS256 alg to do jwt authentication. I write my secret key in secret.txt.
I use following Caddyfile :
:8080 {
gzip
log access.log

jwt {
path /
secret /usr/local/caddy_jwt/secret.txt
allow user aaron
allow user leo
}

proxy /api xx.xx.xx.xx:80
}

I issue get request with valid jwt token. But got 401 status code .
I switch to use JWT_SECRET env . it work.

Caddy 2 port

@mholt has announced that Caddy 2 may hit stable by early next year. Based on a quick glance at the new docs, there are lots of changes to how plugins (soon to be called modules) work, and it will likely require a complete rewrite to support JWT auth in Caddy 2. I plan to post a deprecation notice on the readme so people know that no further development will happen on this plugin unless it's to address a security vulnerability.

I haven't decided whether I'll port this to the new module system as I rarely use JWT anymore. I think paseto has a better design and less foot guns if you are starting from scratch on a new application.

This issue is meant as a discussion area for a potential port to Caddy v2. For anyone considering an auth module for Caddy, I'd offer the following ideas based on several years of experience hearing about various use cases.

Eliminate all Caddyfile-style configuration directives that express auth logic

One of the major drawbacks of v1-style plugins was the use of the Caddyfile for expressing auth logic. While the system of allow user bob worked, it's awkward to use a configuration language to express logic. This is a recurring problem with projects that use config files to express branching logic that depends on external state (e.g., Ansible, Kubernetes Helm templates, etc.) where if-then-else logic is bolted on to a declarative config file format. This can be solved by using Starlark as suggested below, but there may be other ways to move this logic outside Caddy configuration and into an environment that is more suitable to scripting. One option is to delegate auth decisions to a separate microservice as in caddy-extauth, so that authorization can be controlled by the user in any language, but easily integrated as a middleware-type plugin.

The new module should take advantage of the integrated Starlark scripting capability to provide a platform for users to express advanced auth flows.

Starlark has a python-like syntax that would allow someone to express all kinds of auth flows and special cases that are awkwardly handled as edge cases in this plugin. Things like extracting tokens from oddly named cookies, stripping claim prefixes, and returning non-standard HTTP responses would be relatively easy in Starlark and keep these edge cases out of the module code.

token = token_from_cookie("my-auth-token")
secret = secret_from_env("JWT_SECRET")

if claim(token, "user") == "bob" and valid(token, HS256, secret):
     return ok()
else:
     return redirect(301, "https://mysite.com/login")

The starlark runtime can be injected with custom functions (implemented in Go), such as valid(token, algorithm, secret) and claim(token, field), that would extract claims and validate tokens, return various HTTP responses, set headers, allow fine grained auth rules for different paths, etc. so that nearly any use case can be under the control of the user while keeping the module code free of special cases.

Address drawbacks of delegating auth decisions to user scripts

One downside of the ideas above is that it would be a lot easier for a user to shoot themselves in the foot unless they are familiar with the design errors in JWT. There are several known vulnerabilities that arise from the JWT specification, like attacks based on monkeying with the alg header field in order to control how JWT validation is applied.

Care should be taken in what functions are supplied to the user so that the design of the API prevents them from insecurely validating tokens.

go mod errors with "version "v3.7.2" invalid" when requiring this package

With go modules, versions v2 and above of a module are supposed to have a trailing /vN on the module name.

When I try to require this package with the published module name, with a line like require github.com/BTBurke/caddy-jwt v3.7.2, I see the following error:

$ go get
go: finding github.com/BTBurke/caddy-jwt v3.7.2
go: finding github.com/BTBurke/caddy-jwt v3.7.2
go: errors parsing go.mod:
/tmp/foo/go.mod:3: require github.com/BTBurke/caddy-jwt: version "v3.7.2" invalid: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3

If I try to use the "correct" name with require github.com/BTBurke/caddy-jwt/v3 v3.7.2, I see:

$ go get
go: github.com/BTBurke/caddy-jwt/[email protected]: go.mod has non-.../v3 module path "github.com/BTBurke/caddy-jwt" (and .../v3/go.mod does not exist) at revision v3.7.2

(yeah, go modules are a pain)

The fix, from what I can tell, is to alter the module line in go.mod to append /v3. I'll open a PR for that soon!

Multiple auth provider trust.

Is there any way to add several public keys to verify JWT?
I want to trust multiple hosts which provide JWT tokens.

[Feature Request] Allow spcifying keys somewhere else

why do the keys need to be stored in an environment variable? couldnt they also be stored in the caddy file or another file which is kept safely?

also having the keys somewhere else would allow for usage different JSON keys for different paths, for example, different actors allowing access for different paths.

Extra claims from auth0 cause invalid headers

Auth0 provides extra claims in the following way:

  "https://auth.mydomain.com/app_metadata": {
    "authorization": {
      "user": "o1ek",
      "groups": [
        "group1",
        "group2"
      ]
    }

It causes the following errors:

14/Aug/2017:15:04:09 +0200 [ERROR 502 /] net/http: invalid header field name "Token-Claim-HTTPS://AUTH.MYDOMAIN.COM/APP_METADATA.AUTHORIZATION.GROUPS"
14/Aug/2017:15:04:10 +0200 [ERROR 502 /favicon.ico] net/http: invalid header field name "Token-Claim-HTTPS://AUTH.MYDOMAIN.COM/APP_METADATA.AUTHORIZATION.USER"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.