-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is an (initial) implementation of RFC 8414 and provides server metadata under the `/.well-known/openid-configuration` URL. This also moves the JWKS URL to `/certs`.
- Loading branch information
Showing
10 changed files
with
343 additions
and
128 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package oauth2 | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"encoding/json" | ||
"math/big" | ||
"net/http" | ||
"net/http/httptest" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestAuthorizationServer_handleJWKS(t *testing.T) { | ||
type fields struct { | ||
clients []*Client | ||
signingKeys map[int]*ecdsa.PrivateKey | ||
} | ||
type args struct { | ||
r *http.Request | ||
} | ||
tests := []struct { | ||
name string | ||
fields fields | ||
args args | ||
want *JSONWebKeySet | ||
wantCode int | ||
}{ | ||
{ | ||
name: "retrieve JWKS with GET", | ||
fields: fields{ | ||
signingKeys: map[int]*ecdsa.PrivateKey{ | ||
0: { | ||
PublicKey: ecdsa.PublicKey{ | ||
Curve: elliptic.P256(), | ||
X: big.NewInt(1), | ||
Y: big.NewInt(2), | ||
}, | ||
}, | ||
}, | ||
}, | ||
args: args{ | ||
r: httptest.NewRequest("GET", "/certs", nil), | ||
}, | ||
want: &JSONWebKeySet{ | ||
Keys: []JSONWebKey{{ | ||
Kid: "0", | ||
Kty: "EC", | ||
Crv: "P-256", | ||
X: "AQ", | ||
Y: "Ag", | ||
}}, | ||
}, | ||
wantCode: http.StatusOK, | ||
}, | ||
{ | ||
name: "retrieve JWKS with POST", | ||
fields: fields{}, | ||
args: args{ | ||
r: httptest.NewRequest("POST", "/certs", nil), | ||
}, | ||
want: nil, | ||
wantCode: http.StatusMethodNotAllowed, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
srv := &AuthorizationServer{ | ||
clients: tt.fields.clients, | ||
signingKeys: tt.fields.signingKeys, | ||
} | ||
|
||
rr := httptest.NewRecorder() | ||
srv.handleJWKS(rr, tt.args.r) | ||
|
||
gotCode := rr.Code | ||
if gotCode != tt.wantCode { | ||
t.Errorf("AuthorizationServer.handleJWKS() code = %v, wantCode %v", gotCode, tt.wantCode) | ||
} | ||
|
||
if rr.Code == http.StatusOK { | ||
var got JSONWebKeySet | ||
err := json.Unmarshal(rr.Body.Bytes(), &got) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
if !reflect.DeepEqual(&got, tt.want) { | ||
t.Errorf("AuthorizationServer.handleJWKS() = %v, want %v", got, tt.want) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package oauth2 | ||
|
||
import ( | ||
"net/http" | ||
) | ||
|
||
// ServerMetadata is a struct that contains metadata according to RFC 8414. | ||
// | ||
// See https://datatracker.ietf.org/doc/rfc8414/. | ||
type ServerMetadata struct { | ||
Issuer string `json:"issuer"` | ||
AuthorizationEndpoint string `json:"authorization_endpoint"` | ||
TokenEndpoint string `json:"token_endpoint"` | ||
JWKSURI string `json:"jwks_uri"` | ||
SupportedScopes []string `json:"scopes_supported"` | ||
SupportedResponseTypes []string `json:"response_types_supported"` | ||
SupportedGrantTypes []string `json:"grant_types_supported"` | ||
} | ||
|
||
// buildMetadata builds a [ServerMetadata] based on the capabilities of this | ||
// server and the public URL. | ||
func buildMetadata(url string) *ServerMetadata { | ||
return &ServerMetadata{ | ||
Issuer: url, | ||
AuthorizationEndpoint: url + "/authorize", | ||
TokenEndpoint: url + "/token", | ||
JWKSURI: url + "/certs", | ||
SupportedScopes: []string{"profile"}, | ||
SupportedResponseTypes: []string{"code"}, | ||
SupportedGrantTypes: []string{"authorization_code", "client_credentials", "refresh_token"}, | ||
} | ||
} | ||
|
||
func (srv *AuthorizationServer) handleMetadata(w http.ResponseWriter, r *http.Request) { | ||
if r.Method != "GET" { | ||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed) | ||
return | ||
} | ||
|
||
writeJSON(w, srv.metadata) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package oauth2 | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func Test_buildMetadata(t *testing.T) { | ||
type args struct { | ||
url string | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want *ServerMetadata | ||
}{ | ||
{ | ||
name: "Happy path", | ||
args: args{ | ||
url: "http://localhost:8000", | ||
}, | ||
want: &ServerMetadata{ | ||
Issuer: "http://localhost:8000", | ||
AuthorizationEndpoint: "http://localhost:8000/authorize", | ||
TokenEndpoint: "http://localhost:8000/token", | ||
JWKSURI: "http://localhost:8000/certs", | ||
SupportedScopes: []string{"profile"}, | ||
SupportedResponseTypes: []string{"code"}, | ||
SupportedGrantTypes: []string{"authorization_code", "client_credentials", "refresh_token"}, | ||
}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := buildMetadata(tt.args.url); !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("buildMetadata() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestAuthorizationServer_handleMetadata(t *testing.T) { | ||
type fields struct { | ||
metadata *ServerMetadata | ||
} | ||
type args struct { | ||
r *http.Request | ||
} | ||
tests := []struct { | ||
name string | ||
fields fields | ||
args args | ||
want *ServerMetadata | ||
wantCode int | ||
}{ | ||
{ | ||
name: "wrong method", | ||
fields: fields{}, | ||
args: args{ | ||
r: httptest.NewRequest("POST", "/.well-known/openid-configuration", nil), | ||
}, | ||
want: nil, | ||
wantCode: http.StatusMethodNotAllowed, | ||
}, | ||
{ | ||
name: "valid metadata", | ||
fields: fields{ | ||
metadata: buildMetadata(DefaultAddress), | ||
}, | ||
args: args{ | ||
r: httptest.NewRequest("GET", "/.well-known/openid-configuration", nil), | ||
}, | ||
want: buildMetadata(DefaultAddress), | ||
wantCode: 200, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
srv := &AuthorizationServer{ | ||
metadata: tt.fields.metadata, | ||
} | ||
|
||
rr := httptest.NewRecorder() | ||
srv.handleMetadata(rr, tt.args.r) | ||
|
||
gotCode := rr.Code | ||
if gotCode != tt.wantCode { | ||
t.Errorf("AuthorizationServer.handleMetadata() code = %v, wantCode %v", gotCode, tt.wantCode) | ||
} | ||
|
||
if rr.Code == http.StatusOK { | ||
var got ServerMetadata | ||
err := json.Unmarshal(rr.Body.Bytes(), &got) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
if !reflect.DeepEqual(&got, tt.want) { | ||
t.Errorf("AuthorizationServer.handleMetadata() = %v, want %v", got, tt.want) | ||
} | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.