auth

Paddy 2015-05-15 Parent:cf1aef6eb81f Child:b7e685839a1b

168:581c60f8dd23 Go to Latest

auth/authcode.go

Switch to a JWT approach. We're going to use a JWT as our access tokens (as discussed in &yet's excellent post https://blog.andyet.com/2015/05/12/micro-services-user-info-and-auth and my ensuing conversation with Fritzy). The benefit of this approach is that we can do authentication and even some authorization without touching the database at all. The drawback is that we can no longer revoke access tokens, only the refresh tokens that grant the access tokens. We need a new config variable to set our private key, used to sign the JWT. We get to remove our token handlers, as we no longer can revoke tokens, so there's no purpose in getting information about it or listing them. Our tokenStore revokeToken gets to be simplified, as it will only ever be used for refresh tokens now. We also updated our postgres and memstore implementations. We added a helper method for generating the signed "access token" (our JWT) and started using it in the places where we're creating a Token. We get to remove the `revoked` SQL column for the tokens table, and rename the `refresh_revoked` column to just be `revoked`. We shortened our access token expiration to 15 minutes instead of an hour, to deal with the token not being revokable.

History
paddy@26 1 package auth
paddy@26 2
paddy@26 3 import (
paddy@84 4 "encoding/json"
paddy@29 5 "errors"
paddy@84 6 "net/http"
paddy@26 7 "time"
paddy@26 8
paddy@107 9 "code.secondbit.org/uuid.hg"
paddy@26 10 )
paddy@26 11
paddy@84 12 func init() {
paddy@84 13 RegisterGrantType("authorization_code", GrantType{
paddy@84 14 Validate: authCodeGrantValidate,
paddy@94 15 Invalidate: authCodeGrantInvalidate,
paddy@84 16 IssuesRefresh: true,
paddy@85 17 ReturnToken: RenderJSONToken,
paddy@123 18 AllowsPublic: true,
paddy@124 19 AuditString: authCodeGrantAuditString,
paddy@84 20 })
paddy@84 21 }
paddy@84 22
paddy@29 23 var (
paddy@87 24 // ErrNoAuthorizationCodeStore is returned when a Context tries to act on a authorizationCodeStore without setting one first.
paddy@87 25 ErrNoAuthorizationCodeStore = errors.New("no authorizationCodeStore was specified for the Context")
paddy@87 26 // ErrAuthorizationCodeNotFound is returned when an AuthorizationCode is requested but not found in the authorizationCodeStore.
paddy@87 27 ErrAuthorizationCodeNotFound = errors.New("authorization code not found in authorizationCodeStore")
paddy@87 28 // ErrAuthorizationCodeAlreadyExists is returned when an AuthorizationCode is added to a authorizationCodeStore, but another AuthorizationCode with the
paddy@87 29 // same Code already exists in the authorizationCodeStore.
paddy@87 30 ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists in authorizationCodeStore")
paddy@29 31 )
paddy@29 32
paddy@87 33 // AuthorizationCode represents an authorization grant made by a user to a Client, to
paddy@57 34 // access user data within a defined Scope for a limited amount of time.
paddy@87 35 type AuthorizationCode struct {
paddy@26 36 Code string
paddy@26 37 Created time.Time
paddy@26 38 ExpiresIn int32
paddy@26 39 ClientID uuid.ID
paddy@163 40 Scopes Scopes
paddy@26 41 RedirectURI string
paddy@26 42 State string
paddy@69 43 ProfileID uuid.ID
paddy@94 44 Used bool
paddy@26 45 }
paddy@26 46
paddy@87 47 type authorizationCodeStore interface {
paddy@87 48 getAuthorizationCode(code string) (AuthorizationCode, error)
paddy@87 49 saveAuthorizationCode(authCode AuthorizationCode) error
paddy@87 50 deleteAuthorizationCode(code string) error
paddy@163 51 deleteAuthorizationCodesByProfileID(profileID uuid.ID) error
paddy@164 52 deleteAuthorizationCodesByClientID(clientID uuid.ID) error
paddy@94 53 useAuthorizationCode(code string) error
paddy@26 54 }
paddy@29 55
paddy@87 56 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) {
paddy@87 57 m.authCodeLock.RLock()
paddy@87 58 defer m.authCodeLock.RUnlock()
paddy@87 59 authCode, ok := m.authCodes[code]
paddy@29 60 if !ok {
paddy@87 61 return AuthorizationCode{}, ErrAuthorizationCodeNotFound
paddy@29 62 }
paddy@87 63 return authCode, nil
paddy@29 64 }
paddy@29 65
paddy@87 66 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error {
paddy@87 67 m.authCodeLock.Lock()
paddy@87 68 defer m.authCodeLock.Unlock()
paddy@87 69 _, ok := m.authCodes[authCode.Code]
paddy@29 70 if ok {
paddy@87 71 return ErrAuthorizationCodeAlreadyExists
paddy@29 72 }
paddy@87 73 m.authCodes[authCode.Code] = authCode
paddy@29 74 return nil
paddy@29 75 }
paddy@29 76
paddy@87 77 func (m *memstore) deleteAuthorizationCode(code string) error {
paddy@87 78 m.authCodeLock.Lock()
paddy@87 79 defer m.authCodeLock.Unlock()
paddy@87 80 _, ok := m.authCodes[code]
paddy@29 81 if !ok {
paddy@87 82 return ErrAuthorizationCodeNotFound
paddy@29 83 }
paddy@87 84 delete(m.authCodes, code)
paddy@29 85 return nil
paddy@29 86 }
paddy@84 87
paddy@163 88 func (m *memstore) deleteAuthorizationCodesByProfileID(profileID uuid.ID) error {
paddy@163 89 m.authCodeLock.Lock()
paddy@163 90 defer m.authCodeLock.Unlock()
paddy@163 91 var codes []string
paddy@163 92 for _, code := range m.authCodes {
paddy@163 93 if code.ProfileID.Equal(profileID) {
paddy@163 94 codes = append(codes, code.Code)
paddy@163 95 }
paddy@163 96 }
paddy@163 97 if len(codes) < 1 {
paddy@163 98 return ErrProfileNotFound
paddy@163 99 }
paddy@163 100 for _, code := range codes {
paddy@163 101 delete(m.authCodes, code)
paddy@163 102 }
paddy@163 103 return nil
paddy@163 104 }
paddy@163 105
paddy@164 106 func (m *memstore) deleteAuthorizationCodesByClientID(clientID uuid.ID) error {
paddy@164 107 m.authCodeLock.Lock()
paddy@164 108 defer m.authCodeLock.Unlock()
paddy@164 109 var codes []string
paddy@164 110 for _, code := range m.authCodes {
paddy@164 111 if code.ClientID.Equal(clientID) {
paddy@164 112 codes = append(codes, code.Code)
paddy@164 113 }
paddy@164 114 }
paddy@164 115 if len(codes) < 1 {
paddy@164 116 return ErrClientNotFound
paddy@164 117 }
paddy@164 118 for _, code := range codes {
paddy@164 119 delete(m.authCodes, code)
paddy@164 120 }
paddy@164 121 return nil
paddy@164 122 }
paddy@164 123
paddy@94 124 func (m *memstore) useAuthorizationCode(code string) error {
paddy@94 125 m.authCodeLock.Lock()
paddy@94 126 defer m.authCodeLock.Unlock()
paddy@94 127 a, ok := m.authCodes[code]
paddy@94 128 if !ok {
paddy@94 129 return ErrAuthorizationCodeNotFound
paddy@94 130 }
paddy@94 131 a.Used = true
paddy@94 132 m.authCodes[code] = a
paddy@94 133 return nil
paddy@94 134 }
paddy@94 135
paddy@163 136 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
paddy@84 137 enc := json.NewEncoder(w)
paddy@84 138 code := r.PostFormValue("code")
paddy@84 139 if code == "" {
paddy@84 140 w.WriteHeader(http.StatusBadRequest)
paddy@84 141 renderJSONError(enc, "invalid_request")
paddy@84 142 return
paddy@84 143 }
paddy@123 144 clientID, _, ok := getClientAuth(w, r, true)
paddy@123 145 if !ok {
paddy@84 146 return
paddy@84 147 }
paddy@87 148 authCode, err := context.GetAuthorizationCode(code)
paddy@84 149 if err != nil {
paddy@87 150 if err == ErrAuthorizationCodeNotFound {
paddy@84 151 w.WriteHeader(http.StatusBadRequest)
paddy@84 152 renderJSONError(enc, "invalid_grant")
paddy@84 153 return
paddy@84 154 }
paddy@84 155 w.WriteHeader(http.StatusInternalServerError)
paddy@84 156 renderJSONError(enc, "server_error")
paddy@84 157 return
paddy@84 158 }
paddy@85 159 redirectURI := r.PostFormValue("redirect_uri")
paddy@87 160 if authCode.RedirectURI != redirectURI {
paddy@84 161 w.WriteHeader(http.StatusBadRequest)
paddy@84 162 renderJSONError(enc, "invalid_grant")
paddy@84 163 return
paddy@84 164 }
paddy@87 165 if !authCode.ClientID.Equal(clientID) {
paddy@84 166 w.WriteHeader(http.StatusBadRequest)
paddy@84 167 renderJSONError(enc, "invalid_grant")
paddy@84 168 return
paddy@84 169 }
paddy@135 170 return authCode.Scopes, authCode.ProfileID, true
paddy@84 171 }
paddy@90 172
paddy@90 173 func authCodeGrantInvalidate(r *http.Request, context Context) error {
paddy@94 174 code := r.PostFormValue("code")
paddy@94 175 if code == "" {
paddy@94 176 return ErrAuthorizationCodeNotFound
paddy@94 177 }
paddy@94 178 return context.UseAuthorizationCode(code)
paddy@90 179 }
paddy@124 180
paddy@124 181 func authCodeGrantAuditString(r *http.Request) string {
paddy@124 182 return "authcode:" + r.PostFormValue("code")
paddy@124 183 }