btburke / caddy-jwt Goto Github PK
View Code? Open in Web Editor NEWJWT middleware for the Caddy server
License: MIT License
JWT middleware for the Caddy server
License: MIT License
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.
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?
I understand the version of caddy-jwt in caddy does not include RSA - see https://caddyserver.com/docs/jwt
I read #15 (comment)
How can contributions speed up inclusion of recent caddy-jwt with RSA support in upstream caddy release?
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.
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
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.
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.
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
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)
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!
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
}
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).
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:
JWT_SECRET
environment variable.secret
option./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
.777
.jsonwebtoken
and it's valid.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.
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.
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.
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?
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.
With jwt: Reading files and directories.
With basicauth or no auth at all: Everything.
With jwt: Uploading, editing, renaming and deleting files as well as adding directories.
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
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.
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.
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?
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?
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.
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.
Hi, I'm not sure if I'm doing something wrong or if this is a bug..
localhost {
log stdout
jwt /protected
}
env JWT_SECRET=testing caddy
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.
@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.
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?
Unsure, but I think this line:
https://github.com/BTBurke/caddy-jwt/blob/master/keys.go#L102
Should be:
return fmt.Errorf("secret file '%s' does not exist", instance.filename)
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.
So i want get a custom claims as var and proxy that requests.
It's possible with your plugin?
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"
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.
I would like to be able to use this for Apple PassKit passes, which means checking the Authorization
header for a ApplePass <token>
instead of Bearer <token>
. Can we relax the check for Bearer
to allow any string, or a configurable one?
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.)
Is there any way to add several public keys to verify JWT?
I want to trust multiple hosts which provide JWT tokens.
Just downloaded a fresh Caddy v0.11.5 with
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
Didn't try the versions in between.
The build fails on h2quic using the new modules build server. Need to:
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.