oauth2
oauth2 copied to clipboard
Optional & Secured JSON response with Redirect URL for code flow
Problem
- Right now the /authorize handler forces a redirect as the response when valid. If the request comes client-side from another domain, the redirect fails because of web security standards. For modern web-apps the frontend can commonly be a universal-rendered-js app using a framework like next or nuxt. A developer may want to implement the authorize flow in the seperate frontend project to preserve the look & feel as well as a potential to use the user's existing session or JWT so they don't have to login again when authorizing a oauth2 client.
Proposal
- Proposal: Allow an option for a non-redirect (JSON response) with the sensitive redirectURL from authorize when using the code
response_type
. To lock down the security, there could be a configuration for whitelisting certain domains / origins (similar to CORS) so that malicious devs couldn't imitate this flow to leak access tokens.
I ended up implementing a workaround for the stack I've been working with where the Frontend is a NextJS universal react app and the backend written in golang. Something like this for the Authorize handler (my ugly override):
func (s *CustomOauthServer) CustomHandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) error {
req, err := s.ValidationAuthorizeRequest(r)
if err != nil {
return s.redirectError(w, req, err)
}
// user authorization
userID, err := s.UserAuthorizationHandler(w, r)
if err != nil {
return s.redirectError(w, req, err)
} else if userID == "" {
return nil
}
req.UserID = userID
// specify the scope of authorization
if fn := s.AuthorizeScopeHandler; fn != nil {
scope, err := fn(w, r)
if err != nil {
return err
} else if scope != "" {
req.Scope = scope
}
}
// specify the expiration time of access token
if fn := s.AccessTokenExpHandler; fn != nil {
exp, err := fn(w, r)
if err != nil {
return err
}
req.AccessTokenExp = exp
}
ti, err := s.GetAuthorizeToken(req)
if err != nil {
return s.redirectError(w, req, err)
}
// If the redirect URI is empty, the default domain provided by the client is used.
if req.RedirectURI == "" {
client, err := s.Manager.GetClient(req.ClientID)
if err != nil {
return err
}
req.RedirectURI = client.GetDomain()
}
crd := r.FormValue("cancel_redirect")
responseData := s.GetAuthorizeData(req.ResponseType, ti)
if crd == "" {
err := s.redirect(w, req, responseData)
return err
} else {
uri, err := s.GetRedirectURI(req, responseData)
if err != nil {
return err
}
// Here would be URL whitelist check would happen here and reject origin domains not in config.
data := map[string]interface{}{"redirect_url": uri}
err = s.noRedirect(w, data, nil)
return err
}
}
func (s *CustomOauthServer) noRedirect(w http.ResponseWriter, data map[string]interface{}, header http.Header, statusCode ...int) error {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Pragma", "no-cache")
for key := range header {
w.Header().Set(key, header.Get(key))
}
status := http.StatusOK
if len(statusCode) > 0 && statusCode[0] > 0 {
status = statusCode[0]
}
w.WriteHeader(status)
return json.NewEncoder(w).Encode(data)
}
Anyone else think this feature might be useful / helpful? I ended up extending the Oauth2Server to workaround the current status of the library.