-
Notifications
You must be signed in to change notification settings - Fork 404
feat: identities getting started #2103
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
base: master
Are you sure you want to change the base?
Changes from all commits
7686156
7ae7fd8
827fa9b
7c4cf53
f851cea
9af2ee8
4935ce5
7546b46
701a16e
558ffa4
a2be8fd
5012ca8
e474483
09117a4
59e7a08
471e4d7
e66305a
87bc9df
57d798c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// dashboard_handler.go | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
) | ||
|
||
// dashboardHandler simply displays the session information | ||
func (app *App) dashboardHandler(writer http.ResponseWriter, request *http.Request) { | ||
// Get the session from context | ||
session, err := getSession(request.Context()) | ||
if err != nil { | ||
http.Error(writer, err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Return the session data as JSON | ||
writer.Header().Set("Content-Type", "application/json") | ||
encoder := json.NewEncoder(writer) | ||
encoder.SetIndent("", " ") | ||
encoder.Encode(session) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module go-start | ||
|
||
go 1.24.0 | ||
|
||
require ( | ||
github.com/ory/client-go v1.20.6 // indirect | ||
golang.org/x/oauth2 v0.29.0 // indirect | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
github.com/ory/client-go v1.20.6 h1:1oUH4y1N8YCgR292FGw+atimSUgafMMIV8sZb36dH5U= | ||
github.com/ory/client-go v1.20.6/go.mod h1:10sCeMADXhQ9GbGhgfSFfj9Pzpnue/ZAVJFSwy/L1FU= | ||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= | ||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
) | ||
|
||
// LoginHandler handles the /login route | ||
func (app *App) loginHandler(writer http.ResponseWriter, request *http.Request) { | ||
// Get cookies from the request | ||
cookies := request.Header.Get("Cookie") | ||
|
||
// Try to verify session with Ory | ||
session, response, err := app.ory.FrontendAPI.ToSession(request.Context()).Cookie(cookies).Execute() | ||
|
||
// If there's an error or session is not active, redirect to login UI | ||
if err != nil || (err == nil && !*session.Active) { | ||
http.Redirect(writer, request, app.tunnelUrl+"/self-service/login/browser", http.StatusSeeOther) | ||
return | ||
} | ||
|
||
// If session is valid, send the session data as JSON response | ||
writer.Header().Set("Content-Type", "application/json") | ||
writer.WriteHeader(http.StatusOK) | ||
// Use io.Copy to copy the response body to the writer | ||
io.Copy(writer, response.Body) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package main | ||
|
||
import ( | ||
"log" | ||
"net/http" | ||
) | ||
|
||
// LogoutHandler handles the /logout route | ||
func (app *App) logoutHandler(writer http.ResponseWriter, request *http.Request) { | ||
// Get cookies from the request | ||
cookies := request.Header.Get("Cookie") | ||
|
||
// Create a logout flow | ||
logoutFlow, _, err := app.ory.FrontendAPI.CreateBrowserLogoutFlow(request.Context()). | ||
Cookie(cookies). | ||
Execute() | ||
|
||
if err != nil { | ||
log.Printf("Error creating logout flow: %v", err) | ||
// Redirect to home page if there's an error | ||
http.Redirect(writer, request, "/", http.StatusSeeOther) | ||
return | ||
} | ||
|
||
// Redirect to the logout URL | ||
http.Redirect(writer, request, logoutFlow.LogoutUrl, http.StatusSeeOther) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// main.go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"os" | ||
|
||
ory "github.com/ory/client-go" | ||
) | ||
|
||
// App holds application state | ||
type App struct { | ||
ory *ory.APIClient | ||
tunnelUrl string | ||
} | ||
|
||
func main() { | ||
// Get tunnel port from environment or use default | ||
tunnelPort := os.Getenv("ORY_SDK_URL") | ||
if tunnelPort == "" { | ||
tunnelPort = "4000" | ||
} | ||
|
||
// Configure baseUrl for local development | ||
baseUrl := fmt.Sprintf("http://localhost:%s", tunnelPort) | ||
|
||
// Configure Ory SDK | ||
configuration := ory.NewConfiguration() | ||
configuration.Servers = ory.ServerConfigurations{{URL: baseUrl}} | ||
|
||
// Create Ory client | ||
oryClient := ory.NewAPIClient(configuration) | ||
|
||
// Initialize application | ||
app := &App{ | ||
ory: oryClient, | ||
tunnelUrl: baseUrl, | ||
} | ||
|
||
// Setup routes | ||
mux := http.NewServeMux() | ||
mux.HandleFunc("/login", app.loginHandler) | ||
mux.HandleFunc("/logout", app.logoutHandler) | ||
mux.HandleFunc("/refresh-session", app.refreshSessionHandler) | ||
mux.Handle("/", app.sessionMiddleware(app.dashboardHandler)) | ||
|
||
// Get port from environment or use default | ||
port := os.Getenv("PORT") | ||
if port == "" { | ||
port = "3000" | ||
} | ||
|
||
// Print tunnel command for convenience | ||
fmt.Printf("Starting server on port %s. Use tunnel with:\n", port) | ||
fmt.Printf("npx @ory/cli tunnel --dev http://localhost:%s\n", port) | ||
|
||
// Start the server | ||
err := http.ListenAndServe(":"+port, mux) | ||
if err != nil { | ||
fmt.Printf("Could not start server: %s\n", err) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// middleware.go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"log" | ||
"net/http" | ||
|
||
ory "github.com/ory/client-go" | ||
) | ||
|
||
func (app *App) sessionMiddleware(next http.HandlerFunc) http.HandlerFunc { | ||
return func(writer http.ResponseWriter, request *http.Request) { | ||
log.Printf("Checking authentication status\n") | ||
|
||
// Pass cookies to Ory's ToSession endpoint | ||
cookies := request.Header.Get("Cookie") | ||
|
||
// Verify session with Ory | ||
session, _, err := app.ory.FrontendAPI.ToSession(request.Context()).Cookie(cookies).Execute() | ||
|
||
// Redirect to login if session doesn't exist or is inactive | ||
if err != nil || (err == nil && !*session.Active) { | ||
log.Printf("No active session, redirecting to login\n") | ||
// Redirect to the login page | ||
http.Redirect(writer, request, "/self-service/login/browser", http.StatusSeeOther) | ||
return | ||
} | ||
// highlight-start | ||
if *session.AuthenticatorAssuranceLevel != "aal2" { | ||
http.Redirect(writer, request, "/self-service/login/browser?aal=aal2", http.StatusSeeOther) | ||
return | ||
} | ||
// highlight-end | ||
|
||
// Add session to context for the handler | ||
ctx := withSession(request.Context(), session) | ||
next.ServeHTTP(writer, request.WithContext(ctx)) | ||
} | ||
} | ||
|
||
func withSession(ctx context.Context, v *ory.Session) context.Context { | ||
return context.WithValue(ctx, "req.session", v) | ||
} | ||
|
||
func getSession(ctx context.Context) (*ory.Session, error) { | ||
session, ok := ctx.Value("req.session").(*ory.Session) | ||
if !ok || session == nil { | ||
return nil, errors.New("session not found in context") | ||
} | ||
return session, nil | ||
} | ||
|
||
// Dashboard page protected by middleware | ||
mux.Handle("/", app.sessionMiddleware(app.dashboardHandler)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// middleware.go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"log" | ||
"net/http" | ||
|
||
ory "github.com/ory/client-go" | ||
) | ||
|
||
func (app *App) sessionMiddleware(next http.HandlerFunc) http.HandlerFunc { | ||
return func(writer http.ResponseWriter, request *http.Request) { | ||
log.Printf("Checking authentication status\n") | ||
|
||
// Pass cookies to Ory's ToSession endpoint | ||
cookies := request.Header.Get("Cookie") | ||
|
||
// Verify session with Ory | ||
session, _, err := app.ory.FrontendAPI.ToSession(request.Context()).Cookie(cookies).Execute() | ||
|
||
// Redirect to login if session doesn't exist or is inactive | ||
if err != nil || (err == nil && !*session.Active) { | ||
log.Printf("No active session, redirecting to login\n") | ||
// Redirect to the login page | ||
http.Redirect(writer, request, "/self-service/login/browser", http.StatusSeeOther) | ||
return | ||
} | ||
|
||
// Add session to context for the handler | ||
ctx := withSession(request.Context(), session) | ||
next.ServeHTTP(writer, request.WithContext(ctx)) | ||
} | ||
} | ||
|
||
func withSession(ctx context.Context, v *ory.Session) context.Context { | ||
return context.WithValue(ctx, "req.session", v) | ||
} | ||
|
||
func getSession(ctx context.Context) (*ory.Session, error) { | ||
session, ok := ctx.Value("req.session").(*ory.Session) | ||
if !ok || session == nil { | ||
return nil, errors.New("session not found in context") | ||
} | ||
return session, nil | ||
} | ||
|
||
// Dashboard page protected by middleware | ||
mux.Handle("/", app.sessionMiddleware(app.dashboardHandler)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// ory_client.go | ||
package main | ||
|
||
import ( | ||
"os" | ||
|
||
ory "github.com/ory/client-go" | ||
) | ||
|
||
// ConfigureOryClient sets up the Ory client for local development with tunnel | ||
func ConfigureOryClient() (*ory.APIClient, string) { | ||
baseUrl := os.Getenv("ORY_SDK_URL") | ||
|
||
// Configure Ory SDK | ||
configuration := ory.NewConfiguration() | ||
configuration.Servers = ory.ServerConfigurations{{URL: baseUrl}} | ||
|
||
// Create and return client | ||
return ory.NewAPIClient(configuration), baseUrl | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// refresh_handler.go | ||
package main | ||
|
||
import ( | ||
"net/http" | ||
) | ||
|
||
// RefreshSessionHandler handles the /refresh-session route | ||
func (app *App) refreshSessionHandler(writer http.ResponseWriter, request *http.Request) { | ||
// Redirect to Ory login UI with refresh=true parameter | ||
http.Redirect(writer, request, app.tunnelUrl+"/self-service/login/browser?refresh=true", http.StatusSeeOther) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
) | ||
|
||
// SignUpHandler handles the /signup route | ||
func (app *App) signUpHandler(writer http.ResponseWriter, request *http.Request) { | ||
// Get cookies from the request | ||
cookies := request.Header.Get("Cookie") | ||
|
||
// Try to verify session with Ory | ||
session, response, err := app.ory.FrontendAPI.ToSession(request.Context()).Cookie(cookies).Execute() | ||
|
||
// If there's an error or session is not active, redirect to login UI | ||
if err != nil || (err == nil && !*session.Active) { | ||
http.Redirect(writer, request, app.tunnelUrl+"/self-service/registration/browser", http.StatusSeeOther) | ||
return | ||
} | ||
|
||
// If session is valid, send the session data as JSON response | ||
writer.Header().Set("Content-Type", "application/json") | ||
writer.WriteHeader(http.StatusOK) | ||
// Use io.Copy to copy the response body to the writer | ||
io.Copy(writer, response.Body) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
app.get("/", (req, res) => { | ||
ory | ||
.toSession({ cookie: req.header("cookie") }) | ||
.then((data) => res.json(data)) | ||
.catch(() => | ||
res.redirect(`${process.env.ORY_SDK_URL}/self-service/login/browser`), | ||
) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Create logout route | ||
app.get("/logout", async (req, res) => { | ||
try { | ||
// Create a logout flow | ||
const { logout_url } = await ory.createBrowserLogoutFlow({ | ||
cookie: req.header("cookie"), | ||
}) | ||
// Redirect to logout URL | ||
res.redirect(logout_url) | ||
} catch (err) { | ||
res.redirect("/") | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
const requireAuth = async (req, res, next) => { | ||
try { | ||
const session = await ory.toSession({ cookie: req.header("cookie") }) | ||
// highlight-start | ||
if (session.authenticator_assurance_level === "aal2") { | ||
req.session = session | ||
next() | ||
} else { | ||
res.redirect( | ||
`${process.env.ORY_SDK_URL}/self-service/login/browser?aal=aal2`, | ||
) | ||
} | ||
// highlight-end | ||
} catch (error) { | ||
res.redirect(`${process.env.ORY_SDK_URL}/self-service/login/browser`) | ||
} | ||
} | ||
|
||
app.get("/", requireAuth, (req, res) => { | ||
res.json(req.session.identity.traits) // { email: '[email protected]' } | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const requireAuth = async (req, res, next) => { | ||
try { | ||
const session = await ory.toSession({ cookie: req.header("cookie") }) | ||
req.session = session | ||
next() | ||
} catch (error) { | ||
res.redirect(`${process.env.ORY_SDK_URL}/self-service/login/browser`) | ||
} | ||
} | ||
|
||
app.get("/", requireAuth, (req, res) => { | ||
res.json(req.session.identity.traits) // { email: '[email protected]' } | ||
}) | ||
Comment on lines
+1
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this be re-used already in sign in /sign up example? you are also partially doing it in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The sign and sign up sections are intentionally flat so the reader can focus on a stripped down version of the code and just care about the core. Then they progress to higher levels of abstraction which is why I created the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
app.get("/refresh-session", async (req, res) => { | ||
// Redirect to login with refresh=true parameter | ||
res.redirect(`${baseUrl}/ui/login?refresh=true`) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this send some HTML with session info like the express one?