Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have Hydra store usernames linked to tokens #364

Closed
michael-golfi opened this issue Jan 18, 2017 · 7 comments
Closed

Have Hydra store usernames linked to tokens #364

michael-golfi opened this issue Jan 18, 2017 · 7 comments

Comments

@michael-golfi
Copy link

As I understand, OpenID Connect is supposed to be the authentication-side for OAuth (so to speak). Right now I have token creation using client credentials with a backend (called Gatekeeper for all intents and purposes) acting as an intermediary between a webapp and Hydra. But there is no linking of usernames to tokens in Hydra so when introspection is done with Hydra, there is no concept of which user the token was requested for. Which means that I would need to start caching in Gatekeeper. This wouldn't be ideal because I want it to be stateless and to keep the majority of auth logic in Hydra.

I know that client credentials demands that the client id and subject are equal, does Hydra have any way to cache the username of the requesting user (given that the username is provided)?

@michael-golfi
Copy link
Author

michael-golfi commented Jan 18, 2017

Here is the code used to create tokens (for demonstration purposes and since it seems like other people had asked similar questions):

package client

import (
	"net/http"

	ldap "github.com/jtblin/go-ldap-client"
	"github.com/michael-golfi/log4go"
	hydra "github.com/ory-am/hydra/sdk"
	"github.com/spf13/viper"

	"fmt"

	"context"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/clientcredentials"
)

// ClientCredentials uses the OAuth configuration to request access tokens from the OAuth Server
// The server will assume an SSL connection
func ClientCredentials(c *clientcredentials.Config, hydraClient *hydra.Client) func(rw http.ResponseWriter, r *http.Request) {
	bindDn := viper.GetString("ldap.bind")
	bindPassword := viper.GetString("ldap.password")
	baseDn := viper.GetString("ldap.base")
	host := viper.GetString("ldap.host")
	port := viper.GetInt("ldap.port")

	log4go.Info("Connecting to LDAP Server: ldaps://%s:%d\nWith \tbind: %s\n\tbase: %s", host, port, bindDn, baseDn)

	ldapClient := &ldap.LDAPClient{
		Base:               baseDn,
		Host:               host,
		Port:               port,
		UseSSL:             true,
		InsecureSkipVerify: true,

		BindDN:       bindDn,
		BindPassword: bindPassword,
		UserFilter:   "(&(cn=%s)(objectClass=User)(!(objectCategory=Computer)))",
		GroupFilter:  "(memberUid=%s)",
		Attributes:   []string{},
	}

	return func(w http.ResponseWriter, r *http.Request) {
		log4go.Info("Authenticate")

		r.ParseForm()
		username := r.FormValue("username")
		password := r.FormValue("password")

		if username == "" || password == "" {
			log4go.Error("No username or password provided")
			w.WriteHeader(401)
			return
		}

		ok, _, err := ldapClient.Authenticate(username, password)
		if err != nil {
			log4go.Error("Error authenticating user %s: %+v", username, err)
			w.WriteHeader(500)
			return
		}
		if !ok {
			log4go.Error("Authentication failed for user %s", username)
			w.WriteHeader(401)
			return
		}

		defer ldapClient.Close()

		accessToken, err := c.Token(context.Background())
		if err != nil {
			w.WriteHeader(500)
			log4go.Error("Couldn't get a token: %s", err.Error())
			return
		}

		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(translateToken(accessToken)))
	}
}

func translateToken(tok *oauth2.Token) string {
	return fmt.Sprintf(`{"token_type":"%s","expires_in":"%s","access_token":"%s"}`,
		tok.TokenType,
		tok.Expiry,
		tok.AccessToken)
}

@aeneasr
Copy link
Member

aeneasr commented Jan 18, 2017

But there is no linking of usernames to tokens in Hydra so when introspection is done with Hydra, there is no concept of which user the token was requested for.

This isn't true. The subject (user, app, server) that authorized the token is displayed by the sub value when you perform the token introspection. If this doesn't answer your question I'll read through the rest of the issue.

@aeneasr
Copy link
Member

aeneasr commented Jan 18, 2017

The aud value is the subject the token is intended for. Example:

Client cool-app asks user peter for his consent on issuing an access token. peter allows it. Therefore, on token introspection, you'll get:

aud: cool-app
subject: peter
exp: ...
iat: ...
...

@michael-golfi
Copy link
Author

Sorry I didn't mean to be rude, I'm more confused than anything. I was hoping you might be able to clear up some of that... Been banging my head against the wall for a little while now :/. When performing client credentials the sub is the same as the client id right?

So maybe I'm more confused about the flows themselves. If I use authorization code flow then can I get the username as the subject?

@aeneasr
Copy link
Member

aeneasr commented Jan 18, 2017

Oh, I misunderstood you then. I think you're confused about the OAuth2 flows, I highly recommend reading: https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2

TL;DR the client_credential flow does not involve a user. The flow is intended for programmatic APIs that require an access token. If you're looking for getting a token on behalf of a user, use the implicit or authorize_code flows

@michael-golfi
Copy link
Author

Ok thank you so much. I'm going to close for now. I will also provide some code for others if they end up in a similar situation once I'm done this!

@aeneasr
Copy link
Member

aeneasr commented Jan 18, 2017

You're welcome. Always happy to be of help :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants