oauth2 icon indicating copy to clipboard operation
oauth2 copied to clipboard

Help Needed for Adding CustomClaims(Role/Country etc) of User

Open dharmjit opened this issue 5 years ago • 7 comments

Hi Team, I have created a basic setup for Oauth2(Password grant) using this library. Its working, but I need to add more custom claims. Currently only userid is added as below.

    "UserID": "[email protected]",
    "Role": "",
    "Country": ""
  }

My Question is do I need to make multiple DB queries if I needed to add these claims. Also I am not able to understand the entrypoint to this method. The last call I see is to Manager.GenerateAccessToken(gt, tgr)

Token(data *oauth2.GenerateBasic, isGenRefresh bool) Thanks in advance

dharmjit avatar Mar 09 '19 17:03 dharmjit

UserID is a string type, you can encode the data you need into a string using JSON encoding.

LyricTian avatar Mar 11 '19 01:03 LyricTian

you means to say to use userID for all the claims

dharmjit avatar Mar 11 '19 13:03 dharmjit

Yeah, that's definitely not the best way to do this.

I think there's some confusion here. When you're saying "claims", I guess you're referring to JWT which this library doesn't use by default. It does use and generate tokens, but not JWT. That's why you can't add 'claims' to the default token model, because it's not a JWT and there's no such thing as 'claims', just a replaceable AccessGenerate struct to generate the access_token.

So, how is the access token generated?

If you take a look at the code in the manager.go:GenerateAccessToken, the access_token is generated here

It instantiates the default AccessGenerate: generates/access.go In that file, the Token() method generates the access_token itself.

After generating the access_token, the token info is stored in the token store to (I believe, correct me if I'm wrong @LyricTian) later retrieve the token info from the store (in memory or persistent), rather than decoding the access_token and to store the token so you can e.g. invalidate it —and that kind of stuff— with the tokenStore (you can use the tokenStore methods for that).

How to create a custom access token?

Good news are that you CAN change the way the access token is generated, as the README states: you CAN use JWT to generate access tokens.

How? Easy. Just use a custom AccessGenerate struct like the one in generates/access.go . The library itself includes here: generates/jwt_access.go the JWTAccessGenerate you need to create custom access_token with JWT.

Change that file as you want with the claims you want:

func (a *JWTAccessGenerate) Token(data *oauth2.GenerateBasic, isGenRefresh bool) (access, refresh string, err error) {
	claims := &JWTAccessClaims{
		ClientID:  data.Client.GetID(),
		UserID:    data.UserID,
		ExpiredAt: data.TokenInfo.GetAccessCreateAt().Add(data.TokenInfo.GetAccessExpiresIn()).Unix(),
	}

	token := jwt.NewWithClaims(a.SignedMethod, claims)
	var key interface{}

and then change the AccessGenerate used by default with manager.MapAccessGenerate():

import "gopkg.in/oauth2.v3/generates"
import "github.com/dgrijalva/jwt-go"

// ...
manager.MapAccessGenerate(generates.NewJWTAccessGenerate([]byte("00000000"), jwt.SigningMethodHS512))

If you want to parse it and retrieve the claims, use the JWT library (as in the README.md):

import "github.com/dgrijalva/jwt-go"

// Verify jwt access token
token, err := jwt.ParseWithClaims(access, &generates.JWTAccessClaims{}, func(t *jwt.Token) (interface{}, error) {
	if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
		return nil, fmt.Errorf("parse error")
	}
	return []byte("00000000"), nil
})
if err != nil {
	panic(err)
}

claims, ok := token.Claims.(*generates.JWTAccessClaims)
if !ok || !token.Valid {
	panic("invalid token")
}

In my opinion, I would strongly recommend using a custom JWTAccessGenerate to generate your access_token, even if you don't need custom claims.

Just because then you could generate a JWT access_token with asymmetric encryption (like RSA), and you'd have a public and a private key in your auth server and only the public key in the Resource server. This way you can verify and decode tokens from the resource server without additional round trips to the auth server.

To conclude, I would want to thank you @LyricTian for this great library. It's well-coded and it's easy to customize, but as a suggestion I would pick someone who speaks native english (I don't) or open an issue with a 'help needed' label to look for someone, and get him to write up some documentation explaining this sort of things (eg. How is the access token generated, how to change the AccessGenerate, digging into the custom handlers —which I would rename to lifecycle hooks because at first I thought that by 'handlers' you were referring to something related with HTTP because 'handler' is like a convention in golang, also: isGenerateRefresh to allowRefresh.. but that would mean breaking changes so...—, etc.)

Anyway, I hope this helps anyone to add custom JWT claims.

pmrt avatar Mar 21 '19 18:03 pmrt

I would like to thank @pmrt for his answer 👏. Also I would like to ask why not use JWT standard claims. For example this mapping: user_id: "sub" (https://tools.ietf.org/html/rfc7519#section-4.1.2) expire_at: "exp" (https://tools.ietf.org/html/rfc7519#section-4.1.4) client_id: "aud" (https://tools.ietf.org/html/rfc7519#section-4.1.3)

These are also specified here: https://github.com/dgrijalva/jwt-go/blob/06ea1031745cb8b3dab3f6a236daf2b0aa468b7e/claims.go#L18

I've created draft PR if I'm not clear :) ... https://github.com/go-oauth2/oauth2/pull/102

jakubknejzlik avatar Apr 07 '19 10:04 jakubknejzlik

An example of adding public and/or private custom claims would be nice, as this is not obvious to me.

SergeMerzliakov avatar Dec 04 '19 01:12 SergeMerzliakov

I would like to thank @pmrt for his answer 👏. Also I would like to ask why not use JWT standard claims. For example this mapping: user_id: "sub" (https://tools.ietf.org/html/rfc7519#section-4.1.2) expire_at: "exp" (https://tools.ietf.org/html/rfc7519#section-4.1.4) client_id: "aud" (https://tools.ietf.org/html/rfc7519#section-4.1.3)

These are also specified here: https://github.com/dgrijalva/jwt-go/blob/06ea1031745cb8b3dab3f6a236daf2b0aa468b7e/claims.go#L18

I've created draft PR if I'm not clear :) ... #102

Thanks for the PR. 👍

One question about the standard claims: AFAIK, "aud" in OAuth2 refers to the Resource Server that accepts this token, normally it would be the url of the resource. see discussion. "sub" would be relevant to the client_id or user_id depending on which OAuth2 Grant Type is involved (whether it is user-driven or machine-to-machine)

xiaofei-du avatar Feb 11 '21 00:02 xiaofei-du

You are right, at the time of writing my comment I was under impression that aud should be used differently than its actual purpose.

jakubknejzlik avatar Feb 11 '21 06:02 jakubknejzlik