Improve token handling
- add identity information to the index page - let the session expire when the token expires
This commit is contained in:
parent
bc35b0984f
commit
9ad06a2935
10 changed files with 53 additions and 11 deletions
|
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
### Changed
|
||||||
|
- let the session expire when the token expires
|
||||||
|
### Added
|
||||||
|
- add identity output to index page
|
||||||
|
|
||||||
## [0.2.0]
|
## [0.2.0]
|
||||||
### Changed
|
### Changed
|
||||||
- re-order configuration precedence
|
- re-order configuration precedence
|
||||||
|
|
|
@ -128,7 +128,7 @@ func main() {
|
||||||
|
|
||||||
publicURL := buildPublicURL(config.MustString("server.name"), config.MustInt("server.port"))
|
publicURL := buildPublicURL(config.MustString("server.name"), config.MustInt("server.port"))
|
||||||
|
|
||||||
indexHandler, err := handlers.NewIndexHandler(bundle, catalog, ui.Templates, oidcInfo, publicURL)
|
indexHandler, err := handlers.NewIndexHandler(logger, bundle, catalog, oidcInfo, publicURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Fatal("could not initialize index handler")
|
logger.WithError(err).Fatal("could not initialize index handler")
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ func main() {
|
||||||
logging := handlers.Logging(logger)
|
logging := handlers.Logging(logger)
|
||||||
hsts := handlers.EnableHSTS()
|
hsts := handlers.EnableHSTS()
|
||||||
|
|
||||||
errorMiddleware, err := handlers.ErrorHandling(logger, ui.Templates, bundle, catalog)
|
errorMiddleware, err := handlers.ErrorHandling(logger, bundle, catalog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Fatal("could not initialize request error handling")
|
logger.WithError(err).Fatal("could not initialize request error handling")
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ func Authenticate(logger *log.Logger, oauth2Config *oauth2.Config, clientID stri
|
||||||
queryValues := authURL.Query()
|
queryValues := authURL.Query()
|
||||||
queryValues.Set("client_id", clientID)
|
queryValues.Set("client_id", clientID)
|
||||||
queryValues.Set("response_type", "code")
|
queryValues.Set("response_type", "code")
|
||||||
queryValues.Set("scope", "openid offline_access profile email")
|
queryValues.Set("scope", "openid profile email cacert_groups")
|
||||||
queryValues.Set("state", base64.URLEncoding.EncodeToString(services.GenerateKey(oauth2RedirectStateLength)))
|
queryValues.Set("state", base64.URLEncoding.EncodeToString(services.GenerateKey(oauth2RedirectStateLength)))
|
||||||
queryValues.Set("claims", getRequestedClaims(logger))
|
queryValues.Set("claims", getRequestedClaims(logger))
|
||||||
authURL.RawQuery = queryValues.Encode()
|
authURL.RawQuery = queryValues.Encode()
|
||||||
|
|
|
@ -21,12 +21,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"code.cacert.org/cacert/oidc-demo-app/ui"
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -135,12 +136,11 @@ func (w *errorResponseWriter) Write(content []byte) (int, error) {
|
||||||
|
|
||||||
func ErrorHandling(
|
func ErrorHandling(
|
||||||
logger *log.Logger,
|
logger *log.Logger,
|
||||||
templateFS fs.FS,
|
|
||||||
bundle *i18n.Bundle,
|
bundle *i18n.Bundle,
|
||||||
messageCatalog *services.MessageCatalog,
|
messageCatalog *services.MessageCatalog,
|
||||||
) (func(http.Handler) http.Handler, error) {
|
) (func(http.Handler) http.Handler, error) {
|
||||||
errorTemplates, err := template.ParseFS(
|
errorTemplates, err := template.ParseFS(
|
||||||
templateFS,
|
ui.Templates,
|
||||||
"templates/base.gohtml",
|
"templates/base.gohtml",
|
||||||
"templates/errors.gohtml",
|
"templates/errors.gohtml",
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,12 +20,14 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/lestrrat-go/jwx/jwk"
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"code.cacert.org/cacert/oidc-demo-app/ui"
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
||||||
)
|
)
|
||||||
|
@ -33,6 +35,7 @@ import (
|
||||||
type IndexHandler struct {
|
type IndexHandler struct {
|
||||||
bundle *i18n.Bundle
|
bundle *i18n.Bundle
|
||||||
indexTemplate *template.Template
|
indexTemplate *template.Template
|
||||||
|
logger *log.Logger
|
||||||
keySet jwk.Set
|
keySet jwk.Set
|
||||||
logoutURL string
|
logoutURL string
|
||||||
messageCatalog *services.MessageCatalog
|
messageCatalog *services.MessageCatalog
|
||||||
|
@ -72,10 +75,20 @@ func (h *IndexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
accessToken string
|
||||||
|
refreshToken string
|
||||||
idToken string
|
idToken string
|
||||||
ok bool
|
ok bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if accessToken, ok = session.Values[sessionKeyAccessToken].(string); ok {
|
||||||
|
h.logger.WithField("access_token", accessToken).Info("found access token in session")
|
||||||
|
}
|
||||||
|
|
||||||
|
if refreshToken, ok = session.Values[refreshToken].(string); ok {
|
||||||
|
h.logger.WithField("refresh_token", refreshToken).Info("found refresh token in session")
|
||||||
|
}
|
||||||
|
|
||||||
if idToken, ok = session.Values[sessionKeyIDToken].(string); ok {
|
if idToken, ok = session.Values[sessionKeyIDToken].(string); ok {
|
||||||
logoutURL.RawQuery = url.Values{
|
logoutURL.RawQuery = url.Values{
|
||||||
"id_token_hint": []string{idToken},
|
"id_token_hint": []string{idToken},
|
||||||
|
@ -92,6 +105,10 @@ func (h *IndexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expires := oidcToken.Expiration()
|
||||||
|
|
||||||
|
h.logger.WithField("expires", expires).Info("id token expires at")
|
||||||
|
|
||||||
writer.Header().Add("Content-Type", "text/html")
|
writer.Header().Add("Content-Type", "text/html")
|
||||||
|
|
||||||
msgLookup := h.messageCatalog.LookupMessage
|
msgLookup := h.messageCatalog.LookupMessage
|
||||||
|
@ -104,6 +121,10 @@ func (h *IndexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
||||||
"IntroductionText": msgLookup("IndexIntroductionText", nil, localizer),
|
"IntroductionText": msgLookup("IndexIntroductionText", nil, localizer),
|
||||||
"LogoutLabel": msgLookup("LogoutLabel", nil, localizer),
|
"LogoutLabel": msgLookup("LogoutLabel", nil, localizer),
|
||||||
"LogoutURL": logoutURL.String(),
|
"LogoutURL": logoutURL.String(),
|
||||||
|
"AuthenticatedAs": msgLookup("AuthenticatedAs", map[string]interface{}{
|
||||||
|
"Name": oidcToken.Name(),
|
||||||
|
"Email": oidcToken.Email(),
|
||||||
|
}, localizer),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||||
|
@ -113,20 +134,21 @@ func (h *IndexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIndexHandler(
|
func NewIndexHandler(
|
||||||
|
logger *log.Logger,
|
||||||
bundle *i18n.Bundle,
|
bundle *i18n.Bundle,
|
||||||
catalog *services.MessageCatalog,
|
catalog *services.MessageCatalog,
|
||||||
templateFS fs.FS,
|
|
||||||
oidcInfo *services.OIDCInformation,
|
oidcInfo *services.OIDCInformation,
|
||||||
publicURL string,
|
publicURL string,
|
||||||
) (*IndexHandler, error) {
|
) (*IndexHandler, error) {
|
||||||
indexTemplate, err := template.ParseFS(
|
indexTemplate, err := template.ParseFS(
|
||||||
templateFS,
|
ui.Templates,
|
||||||
"templates/base.gohtml", "templates/index.gohtml")
|
"templates/base.gohtml", "templates/index.gohtml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not parse templates: %w", err)
|
return nil, fmt.Errorf("could not parse templates: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &IndexHandler{
|
return &IndexHandler{
|
||||||
|
logger: logger,
|
||||||
bundle: bundle,
|
bundle: bundle,
|
||||||
indexTemplate: indexTemplate,
|
indexTemplate: indexTemplate,
|
||||||
keySet: oidcInfo.KeySet,
|
keySet: oidcInfo.KeySet,
|
||||||
|
|
|
@ -20,6 +20,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/lestrrat-go/jwx/jwk"
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
|
@ -142,6 +143,8 @@ func (c *OidcCallbackHandler) storeTokens(
|
||||||
|
|
||||||
session.Values[sessionKeyIDToken] = idToken
|
session.Values[sessionKeyIDToken] = idToken
|
||||||
|
|
||||||
|
session.Options.MaxAge = int(time.Until(tok.Expiry).Seconds())
|
||||||
|
|
||||||
oidcToken, err := ParseIDToken(idToken, c.keySet)
|
oidcToken, err := ParseIDToken(idToken, c.keySet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse ID token: %w", err)
|
return fmt.Errorf("could not parse ID token: %w", err)
|
||||||
|
|
|
@ -32,6 +32,11 @@ import (
|
||||||
|
|
||||||
func AddMessages(catalog *MessageCatalog) {
|
func AddMessages(catalog *MessageCatalog) {
|
||||||
messages := make(map[string]*i18n.Message)
|
messages := make(map[string]*i18n.Message)
|
||||||
|
messages["AuthenticatedAs"] = &i18n.Message{
|
||||||
|
ID: "AuthenticatedAs",
|
||||||
|
Other: "The identity provider authenticated your identity as {{ .Name }}" +
|
||||||
|
" with the email address {{ .Email }}.",
|
||||||
|
}
|
||||||
messages["IndexGreeting"] = &i18n.Message{
|
messages["IndexGreeting"] = &i18n.Message{
|
||||||
ID: "IndexGreeting",
|
ID: "IndexGreeting",
|
||||||
Other: "Hello {{ .User }}",
|
Other: "Hello {{ .User }}",
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
[AuthenticatedAs]
|
||||||
|
hash = "sha1-58e33592c806dab9cddd3693c0cfee64a07a0a9b"
|
||||||
|
other = "Der Identity-Provider hat dich als {{ .Name }} mit der E-Mail-Adresse {{ .Email }} identifiziert."
|
||||||
|
|
||||||
[ErrorTitle]
|
[ErrorTitle]
|
||||||
hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
|
hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
|
||||||
other = "Es ist ein Fehler aufgetreten"
|
other = "Es ist ein Fehler aufgetreten"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
AuthenticatedAs = "The identity provider authenticated your identity as {{ .Name }} with the email address {{ .Email }}."
|
||||||
ErrorTitle = "An error has occurred"
|
ErrorTitle = "An error has occurred"
|
||||||
IndexGreeting = "Hello {{ .User }}"
|
IndexGreeting = "Hello {{ .User }}"
|
||||||
IndexIntroductionText = "This is an authorization protected resource"
|
IndexIntroductionText = "This is an authorization protected resource"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
|
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
|
||||||
<h1>{{ .Greeting }}</h1>
|
<h1>{{ .Greeting }}</h1>
|
||||||
<p>{{ .IntroductionText }}</p>
|
<p>{{ .IntroductionText }}</p>
|
||||||
|
<p>{{ .AuthenticatedAs }}</p>
|
||||||
<a class="btn btn-outline-primary" href="{{ .LogoutURL }}">{{ .LogoutLabel }}</a>
|
<a class="btn btn-outline-primary" href="{{ .LogoutURL }}">{{ .LogoutLabel }}</a>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
Loading…
Reference in a new issue