Last active
April 19, 2022 18:14
-
-
Save shanna/2139479a94d2cb3cb1227d7b91c455df to your computer and use it in GitHub Desktop.
Wails OIDC OAuth2 minimal inline hack.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Minimal Wails OIDC in-window proof of concept. | |
*/ | |
package main | |
import ( | |
"context" | |
"fmt" | |
"net/http" | |
"github.com/coreos/go-oidc/v3/oidc" | |
"github.com/grokify/go-pkce" | |
"golang.org/x/oauth2" | |
) | |
// Wails V2 application URI. | |
const FrontendURI = "wails://wails/" | |
const OAuth2Issuer = "https://accounts.google.com" | |
// The Go HTTP endpoint we'll spin up to handle oauth2 redirects. | |
// | |
// In a real app you'd want to use an ephemeral port. | |
var OAuth2RedirectURL = "http://localhost:9991/auth/callback" | |
type Claims struct { | |
Email string `json:"email"` | |
Verified bool `json:"email_verified"` | |
} | |
// App struct | |
type App struct { | |
ctx context.Context | |
provider *oidc.Provider | |
auth *oauth2.Config | |
claims *Claims | |
verifier string | |
} | |
func NewApp() *App { | |
verifier := pkce.NewCodeVerifier() | |
// OIDC though you can just create the URLs yourself. | |
provider, _ := oidc.NewProvider(context.Background(), OAuth2Issuer) | |
// I don't get why bug Google seems to require a secret with PKCE still unless I missing something in my | |
// implementation? | |
// | |
// Google generates a secret for web app clients still and I don't seem to be able to omit it or provide a dummy | |
// value. Looking around Stack Overflow I see a lot of confusion. | |
auth := &oauth2.Config{ | |
Endpoint: provider.Endpoint(), | |
ClientID: "client-id", | |
ClientSecret: "not-so-secret-secret", | |
Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, | |
RedirectURL: OAuth2RedirectURL, | |
} | |
app := &App{ | |
provider: provider, | |
auth: auth, | |
verifier: verifier, // TODO: Needs storage if you don't want to log in each app-start. | |
} | |
return app | |
} | |
func (a *App) startup(ctx context.Context) { | |
a.ctx = ctx | |
// TODO: The handler path needs to be whatever you set up on the OIDC provider. | |
http.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) { | |
// TODO: Emit events for auth errors and success? | |
// Check errors on this lot obv: | |
token, _ := a.auth.Exchange( | |
ctx, | |
r.URL.Query().Get("code"), | |
oauth2.SetAuthURLParam(pkce.ParamCodeVerifier, a.verifier), | |
) | |
rawIDToken, _ := token.Extra("id_token").(string) | |
idToken, _ := a.provider.Verifier(&oidc.Config{ClientID: a.auth.ClientID}).Verify(ctx, rawIDToken) | |
_ = idToken.Claims(&a.claims) | |
// Wails needs a runtime.WindowOpenURL(ctx, "url") equivalent of runtime.BrowserOpenURL(ctx, "url") perhaps? If the | |
// window has navigated away from wails://wails then there is no easy way to navigate back unless the page the window | |
// is displaying happens to be under your control (like this handler). If this is the case you can't 301 to | |
// wails://wails but you can in javascript. No idea why. | |
w.Header().Add("Content-Type", "text/html") | |
w.WriteHeader(http.StatusOK) | |
fmt.Fprintf(w, "<script>window.location.href=%q</script>", FrontendURI) | |
}) | |
go http.ListenAndServe(":9991", nil) | |
} | |
// domReady is called after the front-end dom has been loaded | |
func (a App) domReady(ctx context.Context) { | |
// Add your action here | |
} | |
// shutdown is called at application termination | |
func (a *App) shutdown(ctx context.Context) { | |
// Perform your teardown here | |
} | |
// Greet returns a greeting for the given name | |
func (a *App) Greet(name string) string { | |
return fmt.Sprintf("Hello %s!", name) | |
} | |
func (a *App) AuthURL() string { | |
challenge := pkce.CodeChallengeS256(a.verifier) | |
url := a.auth.AuthCodeURL( | |
"state", | |
oauth2.SetAuthURLParam(pkce.ParamCodeChallenge, challenge), | |
oauth2.SetAuthURLParam(pkce.ParamCodeChallengeMethod, pkce.MethodS256), | |
) | |
fmt.Printf("auth url: %s\n", url) | |
return url | |
} | |
func (a *App) AuthEmail() string { | |
if a.claims != nil { | |
return a.claims.Email | |
} | |
return "" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
const login = async () => { | |
window.location.href = await window.go.main.App.AuthURL(); | |
}; | |
let email; | |
window.go.main.App.AuthEmail() | |
.then(e => { | |
if (!e) login(); | |
email = e; | |
}); | |
</script> | |
<main> | |
<h1>Email: {email}</h1> | |
</main> | |
<style> | |
:root { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, | |
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
} | |
main { | |
text-align: center; | |
padding: 1em; | |
margin: 0 auto; | |
} | |
img { | |
height: 16rem; | |
width: 16rem; | |
} | |
h1 { | |
@apply text-3xl font-bold underline text-emerald-700; | |
/*color: #ff3e00; | |
text-transform: uppercase; | |
font-size: 4rem; | |
font-weight: 100;*/ | |
line-height: 1.1; | |
margin: 2rem auto; | |
max-width: 14rem; | |
} | |
p { | |
max-width: 14rem; | |
margin: 1rem auto; | |
line-height: 1.35; | |
} | |
@media (min-width: 480px) { | |
h1 { | |
max-width: none; | |
} | |
p { | |
max-width: none; | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment