This repository has been archived by the owner on Aug 23, 2023. It is now read-only.
/
token.go
158 lines (147 loc) · 5.69 KB
/
token.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package openidvc
import (
"crypto/sha512"
"errors"
"net/http"
"net/url"
"strings"
"time"
"github.com/gorilla/schema"
"github.com/lestrrat-go/jwx/v2/jwa"
)
// OpenId4VCAuthResponse is an open id connect authorizarion response
type TokenRequest struct {
// grant_type is required
GrantType GrantType `schema:"grant_type,omitempty"`
// code is required
Code string `schema:"code,omitempty"`
// client_id is required
ClientId string `schema:"client_id,omitempty"`
// redirect_uri is required
RedirectUri string `schema:"redirect_uri,omitempty"`
// code_verifier is required
CodeVerifier string `schema:"code_verifier,omitempty"`
}
type TokenResponse struct {
AccessToken string `json:"access_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
ExpiresIn time.Duration `json:"expires_in,omitempty"`
CNonce string `json:"c_nonce,omitempty"`
CNonceExpiresIn time.Duration `json:"c_nonce_expires_in,omitempty"`
}
type TokenResponseOption func(*optionalParameters)
// WithAccessTokenSigningKey is the option for the algorithm and signing key
func WithAccessTokenSigningKey(alg jwa.KeyAlgorithm, sigKey interface{}) TokenResponseOption {
return func(t *optionalParameters) {
t.signingKey = sigKey
t.algorithm = alg
}
}
// NewTokenRequest receives an open id token request
func (o *oauth2Request) NewTokenRequest(r *http.Request) (TokenRequest, error) {
var (
tokenRequest TokenRequest
)
if !strings.Contains(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") {
return TokenRequest{}, errors.New("invalid_content_type")
}
err := r.ParseForm()
if err != nil {
return TokenRequest{}, errInternalServerError
}
decoder := schema.NewDecoder()
err = decoder.Decode(&tokenRequest, r.Form)
if err != nil {
return TokenRequest{}, errInternalServerError
}
// Communicating the client secret is done differently per authorization server.
// RFC 6749 says:
// Clients in possession of a client password MAY use the HTTP Basic authentication scheme as defined in [RFC2617] to authenticate with the authorization server.
// Alternatively, the authorization server MAY support including the client credentials in the request-body using parameters
// This authorization server will only accept params, no header value
// Clients such as https://github.com/golang/oauth2 will try both ways, thats why you see 2 incoming requests
// In header style is not accepted
authInHeader := r.Header.Get("Authorization")
if authInHeader != "" {
return TokenRequest{}, errInvalidRequest
}
if err = tokenRequest.Validate(); err != nil {
return TokenRequest{}, err
}
return tokenRequest, nil
}
// Validate validates the token request against rfc6749 and open id connect
func (tr *TokenRequest) Validate() error {
// response_type is required
if tr.GrantType.String() == "" {
return errors.New("missing_grant_type")
} else if !contains([]string{AuthorizationCode_en.String(), AuthorizationCode_en_us.String()}, tr.GrantType.String()) {
return errors.New("unsupported_grant_type")
}
if tr.Code == "" {
return errors.New("missing_code")
}
if tr.RedirectUri == "" {
return errors.New("missing_redirect_uri")
}
// redirect_uri must be a fully qualified domain name (fqdn)
if _, err := url.ParseRequestURI(tr.RedirectUri); err != nil {
return errors.New("invalid_redirect_uri")
}
if tr.ClientId == "" {
return errors.New("missing_client_id")
}
// code_verifier is required
if tr.CodeVerifier == "" {
return errors.New("missing_code_verifier")
}
// verify that the authorization code is valid.
// retrieve the code from the database, its invalid or expired when its not found
if _, err := GetByCodeGranted(tr.Code); err != nil {
return errors.New("invalid_code")
}
// ensure that the redirect_uri parameter value is identical to the redirect_uri parameter value that
// was included in the initial authorization request.
// this test also include that the client id is equal
authRequest, err := GetByClientRedirectUri(tr.ClientId, tr.RedirectUri)
if err != nil {
return errors.New("invalid_client_redirect_uri")
}
// Validate the code verifiers with the earlier received code challenge
if !authRequest.CodeChallengeMethod.Validate(authRequest.CodeChallenge, tr.CodeVerifier) {
return errors.New("invalid_code_verifier")
}
return nil
}
// CreateTokenResponse returns a json token response
// With the options, the signing key, algorithm, and issuer are passed
func (tr *TokenRequest) CreateTokenResponse(options ...TokenResponseOption) (TokenResponse, error) {
authRequest, err := GetByClientRedirectUri(tr.ClientId, tr.RedirectUri)
if err != nil {
return TokenResponse{}, errors.New("invalid_redirect_uri")
}
authorizedCredentialRequest := CredentialRequest{
ClientId: tr.ClientId,
AuthorizationDetails: authRequest.AuthorizationDetails,
CNonceExpiresIn: GetServerConfig().ExpirationTime,
BearerTokenExpiresIn: GetServerConfig().ExpirationTime,
}
authorizedCredentialRequest.CNonce, _ = generateNonce()
accessToken, err := authorizedCredentialRequest.createBearerToken(options...)
if err != nil {
return TokenResponse{}, err
}
authorizedCredentialRequest.BearerTokenSHA512 = sha512.Sum512(accessToken)
// Store the bearer token, so we can verify it up when the credential requests arrives
if err := authorizedCredentialRequest.StoreCredentialRequest(); err != nil {
return TokenResponse{}, err
}
// return the request_uri and expiration time
return TokenResponse{
AccessToken: string(accessToken),
TokenType: "Bearer",
ExpiresIn: authorizedCredentialRequest.BearerTokenExpiresIn,
CNonce: authorizedCredentialRequest.CNonce,
CNonceExpiresIn: authorizedCredentialRequest.CNonceExpiresIn,
}, nil
}