This repository has been archived by the owner on Oct 23, 2024. It is now read-only.
forked from arrikto/oidc-authservice
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstate.go
119 lines (101 loc) · 3.48 KB
/
state.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
// Copyright © 2019 Arrikto Inc. All Rights Reserved.
package main
import (
"encoding/gob"
"net/http"
"net/url"
"time"
"github.com/gorilla/sessions"
"github.com/pkg/errors"
)
const (
oidcStateCookie = "oidc_state_csrf"
sessionValueState = "state"
)
func init() {
gob.Register(State{})
}
type State struct {
// FirstVisitedURL is the URL that the user visited when we redirected them
// to login.
FirstVisitedURL string
}
func newState(firstVisitedURL string) *State {
return &State{
FirstVisitedURL: firstVisitedURL,
}
}
// createState creates the state parameter from the incoming request, stores
// it in the session store and sets a cookie with the session key.
// It returns the session key, which can be used as the state value to start
// an OIDC authentication request.
func createState(r *http.Request, w http.ResponseWriter,
store sessions.Store) (string, error) {
firstVisitedURL, err := url.Parse("")
if err != nil {
return "", errors.Wrap(err, "Failed to initialize empty URL")
}
firstVisitedURL.Path = r.URL.Path
firstVisitedURL.RawPath = r.URL.RawPath
firstVisitedURL.RawQuery = r.URL.RawQuery
s := newState(firstVisitedURL.String())
session := sessions.NewSession(store, oidcStateCookie)
session.Options.MaxAge = int(20 * time.Minute)
session.Options.Path = "/"
session.Values[sessionValueState] = *s
err = session.Save(r, w)
if err != nil {
return "", errors.Wrap(err, "error trying to save session")
}
// Cookie is persisted in ResponseWriter, make a request to parse it.
tempReq := &http.Request{Header: make(http.Header)}
tempReq.Header.Set("Cookie", w.Header().Get("Set-Cookie"))
c, err := tempReq.Cookie(oidcStateCookie)
if err != nil {
return "", errors.Wrap(err, "error trying to save session")
}
return c.Value, nil
}
// verifyState gets the state from the cookie 'initState' saved. It also gets
// the state from an http param and:
// 1. Confirms the two values match (CSRF check).
// 2. Confirms the value is still valid by retrieving the session it points to.
// The state value might be invalid if it has been used before or the session
// expired.
//
// Finally, it returns a State struct, which contains information associated
// with the particular OIDC flow.
func verifyState(r *http.Request, w http.ResponseWriter,
store sessions.Store) (*State, error) {
// Get the state from the HTTP param.
var stateParam = r.FormValue("state")
if len(stateParam) == 0 {
return nil, errors.New("Missing url parameter: state")
}
// Get the state from the cookie the user-agent sent.
stateCookie, err := r.Cookie(oidcStateCookie)
if err != nil {
return nil, errors.Errorf("Missing cookie: '%s'", oidcStateCookie)
}
// Confirm the two values match.
if stateParam != stateCookie.Value {
return nil, errors.New("State value from http params doesn't match " +
"value in cookie. Possible reasons for this error include " +
"opening the login form in more than 1 browser tabs OR a CSRF " +
"attack.")
}
// Retrieve session from store. If it doesn't exist, it may have expired.
session, err := store.Get(r, oidcStateCookie)
if err != nil {
return nil, errors.WithStack(err)
}
if session.IsNew {
return nil, errors.New("State value not found in store, maybe it expired")
}
state := session.Values[sessionValueState].(State)
// Revoke the session so that each state value can only be used once.
if err = revokeSession(r.Context(), w, session); err != nil {
return nil, errors.Wrap(err, "error revoking state session")
}
return &state, nil
}