use "token" as query parameter key to pass tokens (#5647)

the legacy "jwt" query parameter key is still supported.
This commit is contained in:
Alessandro Ros
2026-04-06 17:51:59 +02:00
committed by GitHub
parent 141b789fa9
commit 4472bcfc4b
4 changed files with 123 additions and 34 deletions
+10 -8
View File
@@ -247,7 +247,7 @@ Here's a tutorial on how to setup the [Keycloak identity server](https://www.key
http://localhost:8080/realms/mediamtx/protocol/openid-connect/token
```
The JWT is inside the `access_token` key of the response:
This results in:
```json
{
@@ -262,6 +262,8 @@ Here's a tutorial on how to setup the [Keycloak identity server](https://www.key
}
```
The JWT is inside the `access_token` key.
## Providing username and password
### RTSP
@@ -285,7 +287,7 @@ rtmp://localhost/mystream?user=myuser&pass=mypass
Append username and password to `streamid`:
```
srt://localhost:8890?streamid=publish:mystream:user:pass&pkt_size=1316
srt://localhost:8890?streamid=publish:mystream:myuser:mypass&pkt_size=1316
```
### HLS and WebRTC
@@ -293,7 +295,7 @@ srt://localhost:8890?streamid=publish:mystream:user:pass&pkt_size=1316
Username and password can be passed through the `Authorization: Basic` HTTP header:
```
Authorization: Basic base64(user:pass)
Authorization: Basic base64(myuser:mypass)
```
When using a web browser, a dialog is first shown to users, asking for credentials, and then the header is automatically inserted into every request. If you need to automatically fill credentials from a parent web page, read [Embed streams in a website](17-embed-streams-in-a-website.md).
@@ -301,7 +303,7 @@ When using a web browser, a dialog is first shown to users, asking for credentia
If the `Authorization: Basic` header cannot be used (for instance, in software like OBS Studio, which only allows to provide a "Bearer Token"), credentials can be passed through the `Authorization: Bearer` header (i.e. the "Bearer Token" in OBS), where the value is the concatenation of username and password, separated by a colon:
```
Authorization: Bearer username:password
Authorization: Bearer myuser:mypass
```
## Providing tokens / JWTs
@@ -311,7 +313,7 @@ Authorization: Bearer username:password
Pass the token as a query parameter:
```
rtsp://localhost:8554/mystream?jwt=jwt
rtsp://localhost:8554/mystream?token=mytoken
```
WARNING: FFmpeg implementation of RTSP does not support URLs that are longer than 4096 characters (this is the [MAX_URL_SIZE constant](https://github.com/FFmpeg/FFmpeg/blob/f951aa9ef382d6bb517e05d04d52710f751de427/libavformat/internal.h#L30)), therefore you have to configure your identity server in order to produce JWTs that are shorter than this threshold.
@@ -321,7 +323,7 @@ WARNING: FFmpeg implementation of RTSP does not support URLs that are longer tha
Pass the token as a query parameter:
```
rtmp://localhost/mystream?jwt=jwt
rtmp://localhost/mystream?token=mytoken
```
WARNING: FFmpeg implementation of RTMP does not support URLs that are longer than 1024 characters (this is the [TCURL_MAX_LENGTH constant](https://github.com/FFmpeg/FFmpeg/blob/f951aa9ef382d6bb517e05d04d52710f751de427/libavformat/rtmpproto.c#L55)), therefore you have to configure your identity server in order to produce JWTs that are shorter than this threshold.
@@ -331,7 +333,7 @@ WARNING: FFmpeg implementation of RTMP does not support URLs that are longer tha
Pass the token as password, with an arbitrary user:
```
srt://localhost:8890?streamid=publish:mystream:user:jwt&pkt_size=1316
srt://localhost:8890?streamid=publish:mystream:user:mytoken&pkt_size=1316
```
WARNING: SRT does not support Stream IDs that are longer than 512 characters, therefore you have to configure your identity server in order to produce JWTs that are shorter than this threshold.
@@ -341,7 +343,7 @@ WARNING: SRT does not support Stream IDs that are longer than 512 characters, th
The token can be passed through the `Authorization: Bearer` header:
```
Authorization: Bearer MY_JWT
Authorization: Bearer mytoken
```
In OBS Studio, this is the "Bearer Token" field.
+29 -25
View File
@@ -63,6 +63,32 @@ func matchesPermission(perms []conf.AuthInternalUserPermission, req *Request) bo
return false
}
func getToken(jwtInHTTPQuery bool, req *Request) (string, bool) {
switch {
case req.Credentials.Token != "":
return req.Credentials.Token, true
case req.Credentials.Pass != "":
return req.Credentials.Pass, true
// always allow passing tokens through query parameters with RTSP and RTMP since there's no alternative.
case req.Protocol == ProtocolRTSP || req.Protocol == ProtocolRTMP || (jwtInHTTPQuery && isHTTP(req)):
v, err := url.ParseQuery(req.Query)
if err == nil {
if len(v["token"]) == 1 {
return v["token"][0], true
}
// legacy query key
if len(v["jwt"]) == 1 {
return v["jwt"][0], true
}
}
}
return "", false
}
// Manager is the authentication manager.
type Manager struct {
Method conf.AuthMethod
@@ -222,30 +248,8 @@ func (m *Manager) authenticateJWT(req *Request) (string, error) {
return "", err
}
var encodedJWT string
switch {
case req.Credentials.Token != "":
encodedJWT = req.Credentials.Token
case req.Credentials.Pass != "":
encodedJWT = req.Credentials.Pass
// always allow passing JWT through query parameters with RTSP and RTMP since there's no alternative.
case req.Protocol == ProtocolRTSP || req.Protocol == ProtocolRTMP || (isHTTP(req) && m.JWTInHTTPQuery):
var v url.Values
v, err = url.ParseQuery(req.Query)
if err != nil {
return "", err
}
if len(v["jwt"]) != 1 || len(v["jwt"][0]) == 0 {
return "", fmt.Errorf("JWT not provided")
}
encodedJWT = v["jwt"][0]
default:
token, ok := getToken(m.JWTInHTTPQuery, req)
if !ok {
return "", fmt.Errorf("JWT not provided")
}
@@ -259,7 +263,7 @@ func (m *Manager) authenticateJWT(req *Request) (string, error) {
var cc jwtClaims
cc.permissionsKey = m.JWTClaimKey
_, err = jwt.ParseWithClaims(encodedJWT, &cc, keyfunc, opts...)
_, err = jwt.ParseWithClaims(token, &cc, keyfunc, opts...)
if err != nil {
return "", err
}
+83
View File
@@ -595,6 +595,89 @@ func TestAuthJWT(t *testing.T) {
}
}
func TestAuthJWTQueryParameter(t *testing.T) {
key, err := rsa.GenerateKey(rand.Reader, 1024)
require.NoError(t, err)
httpServ := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
jwk, err2 := jwkset.NewJWKFromKey(key, jwkset.JWKOptions{
Metadata: jwkset.JWKMetadataOptions{
KID: "test-key-id",
},
})
require.NoError(t, err2)
jwkSet := jwkset.NewMemoryStorage()
err2 = jwkSet.KeyWrite(context.Background(), jwk)
require.NoError(t, err2)
response, err2 := jwkSet.JSONPublic(r.Context())
if err2 != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(response)
}),
}
ln, err := net.Listen("tcp", "localhost:4570")
require.NoError(t, err)
go httpServ.Serve(ln)
defer httpServ.Shutdown(context.Background())
for _, ca := range []string{"token", "jwt"} {
t.Run(ca, func(t *testing.T) {
type customClaims struct {
jwt.RegisteredClaims
MediaMTXPermissions []conf.AuthInternalUserPermission `json:"mediamtx_permissions"`
}
claims := customClaims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "test",
Subject: "somebody",
ID: "1",
},
MediaMTXPermissions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
Path: "mypath",
}},
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
token.Header[jwkset.HeaderKID] = "test-key-id"
ss, err2 := token.SignedString(key)
require.NoError(t, err2)
m := Manager{
Method: conf.AuthMethodJWT,
JWTJWKS: "http://localhost:4570/jwks",
JWTClaimKey: "mediamtx_permissions",
}
user, err2 := m.Authenticate(&Request{
Action: conf.AuthActionPublish,
Path: "mypath",
Query: ca + "=" + ss,
Protocol: ProtocolRTSP,
Credentials: &Credentials{
Token: "",
},
IP: net.ParseIP("127.0.0.1"),
})
require.Nil(t, err2)
require.Equal(t, "somebody", user)
})
}
}
func TestAuthJWTExclude(t *testing.T) {
m := Manager{
Method: conf.AuthMethodJWT,
+1 -1
View File
@@ -149,7 +149,7 @@ authJWTClaimKey: mediamtx_permissions
# Actions to exclude from JWT-based authentication.
# Format is the same as the one of user permissions.
authJWTExclude: []
# Allow passing the JWT through query parameters of HTTP requests (i.e. ?jwt=JWT).
# Allow passing the JWT through query parameters of HTTP requests (i.e. ?token=JWT).
# This is a security risk and will be disabled in the future.
# RTSP and RTMP always allow JWT in query even if disabled, since there is no alternative.
authJWTInHTTPQuery: true