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

Single Sign-on #39

Open
dfsek opened this issue Dec 16, 2023 · 6 comments
Open

Single Sign-on #39

dfsek opened this issue Dec 16, 2023 · 6 comments
Milestone

Comments

@dfsek
Copy link

dfsek commented Dec 16, 2023

Hi! I am currently using this behind oauth2-proxy with Keycloak, but would love the ability to integrate directly with OpenID Connect, SAML, or plain old LDAP.

@evan-goode
Copy link
Member

Hey! Yeah, some kind of SSO, probably via OIDC, would be nice and is certainly on the roadmap. As I see it there are a couple approaches:

  1. Have the registration page locked behind SSO. After signing into to SSO, the user can pick a Minecraft username and their Drasl account will be created. The Minecraft launchers will still only do password authentication, so a "Minecraft password" will be randomly generated and can be viewed in the web interface. To log in to the game, the user must paste this password into their Minecraft launcher.
  2. Alternatively, new users can log in to the web interface directly via SSO without visiting the "registration" page. Their account username is provided by SSO provider. Minecraft usernames are limited to 16 characters (and have some additional restrictions) so this approach might not be ideal.
  3. Ideally, users wouldn't need to worry about a "Minecraft password", they would just be able to log in to the launcher through the OAUTH2 identity provider, like how MSA login works now. This would require substantial work to implement on the launcher side.

I feel like approach 1 is probably going to be the best option, although approach 3 would be pretty cool.

@dfsek
Copy link
Author

dfsek commented Jan 28, 2024

I personally think the first option is ideal. Already, Minecraft username is separate from drasl login name, this would also match how MC username is separate from Microsoft/Mojang email/userid. It'd also allow people to change their usernames in SSO-managed accounts. Also, I do agree that OIDC would be the best option.

@dfsek
Copy link
Author

dfsek commented Jan 28, 2024

Additionally, an option to migrate/link existing accounts/usernames on first sign-in to SSO would be awesome! Perhaps on first SSO sign-in, if there are non-SSO accounts, there is an option to sign in with the "local" account, if that is done the user data is transferred from the local account and then the local account is deleted. That is definitely not essential, though. Could just make everyone recreate their accounts haha.

@evan-goode evan-goode mentioned this issue Sep 6, 2024
Closed
7 tasks
@evan-goode evan-goode added this to the 3.0.0 milestone Oct 7, 2024
@xttlegendapi
Copy link

any update on this, this would be great!
I am interested in using oidc OR the drasl api for managing players, but this would be better I think. multiple players per account would also be great

@evan-goode
Copy link
Member

Support for multiple players (#75) has been my focus recently and it's almost done (#107). I'm planning to bump the major version to 3.0.0 when that's released. OIDC is next on my list. I may try to play around with OIDC before releasing 3.0.0; if it turns out to be easy to implement and/or it makes API-breaking changes, it'd be nice to just include it in 3.0.0. I'm planning to work on this over the holidays :)

@xttlegendapi
Copy link

Very cool, thanks for your amazing work on this!

I think oidc would not be too much work, for a other project im using https://github.com/coreos/go-oidc which is pretty simple to use.

I added a code snippet the way I would use it. from there you would just need to implement the DB/session management correctly. maybe it helps.
package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"time"

	"github.com/coreos/go-oidc/v3/oidc"
	"github.com/labstack/echo/v4"
	"golang.org/x/oauth2"
)

var OidcConfig *oauth2.Config
var OidcProvider *oidc.Provider
var OidcVerifier *oidc.IDTokenVerifier

func setupOidc(app *App) {
	ctx := context.Background()
	var err error
	OidcProvider, err = oidc.NewProvider(ctx, app.Config.Oidc.Issuer)
	if err != nil {
		log.Fatal("Error connecting to oidc provider", err)
	}

	oidcConfig := &oidc.Config{
		ClientID: app.Config.Oidc.ClientID,
	}

	OidcConfig = &oauth2.Config{
		ClientID:     app.Config.Oidc.ClientID,
		ClientSecret: app.Config.Oidc.ClientSecret,
		RedirectURL:  app.Config.BaseURL + "/oidc-callback",
		Endpoint:     OidcProvider.Endpoint(),
		Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
	}

	OidcVerifier = OidcProvider.Verifier(oidcConfig)
}

// BaseURL + '/oidc-callback'
func OidcCallback(app *App) func(c echo.Context) error {
	return func(c echo.Context) (err error) {
		ctx := context.Background()
		stateCookie, err := c.Cookie("state")
		if c.QueryParam("state") != stateCookie.Value {
			return errors.New("invalid state")
		}
		if err != nil {
			return err
		}

		oauth2Token, err := OidcConfig.Exchange(ctx, c.QueryParam("code"))
		if err != nil {
			return err
		}

		rawIDToken, ok := oauth2Token.Extra("id_token").(string)
		if !ok {
			return err
		}

		idToken, err := OidcVerifier.Verify(ctx, rawIDToken)
		if err != nil {
			return err
		}

		var claims struct {
			Email         string `json:"email"`
			EmailVerified bool   `json:"email_verified"`
			Name          string `json:"name"`
			Username      string `json:"preferred_username"`
		}

		err = idToken.Claims(&claims)
		if err != nil {
			return err
		}

		// TODO do stuff here with the claims, and create a session for the user

		return nil
	}
}

// when the login button gets clicked, handle a redirect to the oidc provider
func OidcRedirect(app *App) func(c echo.Context) error {
	return func(c echo.Context) (err error) {
		state, err := RandomHex(16)
		if err != nil {
			return err
		}
		c.SetCookie(&http.Cookie{
			Name:     "state",
			Value:    state,
			MaxAge:   int(time.Hour.Seconds()),
			Secure:   true,
			HttpOnly: true,
		})
		return c.Redirect(http.StatusFound, OidcConfig.AuthCodeURL(state))
	}
}

evan-goode added a commit to evan-goode/drasl that referenced this issue Jan 8, 2025
Remaining work is hinted by some TODO comments. Login still
unimplemented (only registration works).

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

No branches or pull requests

3 participants