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

Feature Request: Allow admin to generate a JWT token for any given user #718

Open
dievardump opened this issue Oct 4, 2022 · 8 comments

Comments

@dievardump
Copy link

dievardump commented Oct 4, 2022

Hi, first of all, thanks for your work, pocketbase is just amazing.

I'm mostly working with decentralized stuff (web3 etc...) where emails/password or OAuth are generally not used.

To identify users, we use message signing with privateKey/publicKey schemes:

  • user is always identified by their publicKey
  • server generates a unique message and send to the user
  • user "sign" this message with the corresponding private key
  • server verifies message has been signed with the private key corresponding to the expected public key
  • user is identified

It can not be added as an OAuth2 provider, but what would greatly help would be to allow Admins to generate JWT tokens for any given user.

A bit like:

import PocketBase from 'pocketbase';

const client = new PocketBase('http://127.0.0.1:8090');

await client.admins.authViaEmail('test@example.com', '123456');

// verify custom credentials
const userData = await verifyCredentials(request);

// generate jwt and return to user
const jwtData = await client.users.generateJWTFor(userData.id);

Then the client can auth using this jwt token.

import PocketBase from 'pocketbase';

const client = new PocketBase('http://127.0.0.1:8090');

const jwt = await authCurrentUserWithSomethingElseThanEmailPassword();

// authenticate as regular user
const userData = await client.users.authWithJWT(jwt);
...

This would greatly simplify the task for any of us that are creating and using authentication processes that are not email/pass or OAuth2 based (I'm thinking sms-signing, username/pass, public/private key message signing, etc...)

Because for the moment, to be able to identify my users I actually have to:

  • associate a completely random email address to them (we do not collect any email)
  • create an endpoint that verify my custom credentials (profile.ethereumAddress, unique message, signature)
  • generate a random password & update the user.password
  • send back the random email & newly generated password
  • identify the user with those credentials

This is not so bad, it works and does what it's expected to do, but if I could completely disable the email/password provider and just generate JWTs directly from the SDK with an Admin token, this would be really perfect and feels way less hacky (and ease the process of creating other auth schemes)

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Oct 6, 2022

@dievardump Thanks for the detailed description. Something similar was recently discussed in #725.

After the users refactoring from #376 and the v0.8 release, I'll consider:

  • adding "user impersonate" action for admins
  • adding a helper method in the SDKs to allow sending request with custom auth tokens

@dievardump
Copy link
Author

dievardump commented Oct 6, 2022

Thank you for your answer.

  1. Impersonating users for admin would be a great add for sure.

  2. I'm not really certain to understand the custom token part though. I don't really want it custom, I just want to be able to generate a token that I can send to the front so it can be identified as the user corresponding to this token.

JWT tokens contain information, and I really don't want to mess with the information inside this token (i.e I don't want to custom it). I just want to be able, as an admin, to generate the token 'on the go' given a user id and to be able to transfer it to the front, so it can be used.

I don't really understand how I would identify my user with id a9an7unisc (already in the database) with the client.authStore that you mentioned in #725

How do I get the "YOUR_TOKEN" for a9an7unisc?

@ganigeorgiev
Copy link
Member

ganigeorgiev commented Oct 6, 2022

@dievardump

I'm not really certain to understand the custom token part though. I don't really want it custom, I just want to be able to generate a token that I can send to the front so it can be identified as the user corresponding to this token.

Sorry for the confusion. By "custom" I meant token that differ from the one in the AuthStore because currently in order to send an authenticated request with just a token you have to do something like this:

// "authenticate" as the user associated with the token,
// but since we don't have the user data, we create a dummy object with the minimal required props
client.authStore.save(token, { verified: false })

client.records.getList("example", 1, 50);

In any case, the users impersonate action will provide the functionality that you want (aka. generating an auth token for a user and authenticating with it), but that will be handled after v0.8 release.

@dievardump
Copy link
Author

That's awesome. Thank you for your availability and quick answers.

@gedw99
Copy link

gedw99 commented Oct 7, 2022

This is a really useful use case for me also.

In larger system where PocketbaE in a small part the JWT representing a User is often stored and maintained in sone global system.

For example NATS has its own Auth system and I already am using it with Pocketbase.

The mapping of a NATS jwt or UserID to pocketbaae is kept in NATS and when I call into Pocketbase ( via NATS ), I lookup the one stored in NATS and add it to the Context so that PocketBaae can use it.

I just wanted to highlight this because it seems to relate to this use case, and already started working on this , so the proposed feature of this Issue is very useful to me and possibly others .

@awarebayes
Copy link

I ended up with roll-my-own JWT token, works fine:

// main.go
package main

import (
	"github.com/golang-jwt/jwt/v5"
	"github.com/joho/godotenv"
	"github.com/labstack/echo/v5"
	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/apis"
	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/models"
	"log"
	"os"
	"time"
)

func main() {
	_ = godotenv.Load("../.env")
	app := pocketbase.New()

	app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
		e.Router.GET("/api/get_jwt", func(c echo.Context) error {
			// or to get the authenticated record:
			authRecord, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record)
			if authRecord == nil {
				return apis.NewForbiddenError("Only auth records can access this endpoint", nil)
			}

			id := authRecord.GetId()
			username := authRecord.Username()

			token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
				"id":         id,
				"username":   username,
				"expiration": time.Now().Add(7 * 24 * time.Hour).Unix(),
			})

			privateKey := os.Getenv("ACCESS_PRIVATE_KEY")
			pkStream := []byte(privateKey)

			tokenString, err := token.SignedString(pkStream)

			if err != nil {
				return apis.NewBadRequestError("Something went wrong while signing JWT: "+err.Error(), nil)
			}

			return c.JSON(200,
				map[string]any{"token": tokenString},
			)
		})

		return nil
	})

	if err := app.Start(); err != nil {
		log.Fatal(err)
	}
}

@gedw99
Copy link

gedw99 commented Oct 17, 2023

Passkeys are now standard practice . So would good for this to happen in pocketbase I feel

@danielo515

This comment was marked as off-topic.

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

No branches or pull requests

5 participants