-
Notifications
You must be signed in to change notification settings - Fork 423
docs: oauth getting started #2129
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
Draft
christiannwamba
wants to merge
5
commits into
master
Choose a base branch
from
draft-oauth2-guide
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d0bbe40
docs: oauth getting started
christiannwamba adba8b9
chore: format
christiannwamba 3da9ca9
fix: build failure
christiannwamba 711cdc5
chore: test code examples and resolve feedback
christiannwamba 7326675
chore: format
christiannwamba File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions
63
docs/oauth2-oidc/get-started/_common/code-examples/go/handlers/callback.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Callback handler | ||
func handleCallback(w http.ResponseWriter, r *http.Request) { | ||
// Get session cookie | ||
cookie, err := r.Cookie("session_id") | ||
if err != nil { | ||
http.Error(w, "No session found", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Get session from store | ||
session, ok := sessions[cookie.Value] | ||
if !ok { | ||
http.Error(w, "Invalid session", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Get authorization code and state from query parameters | ||
code := r.URL.Query().Get("code") | ||
state := r.URL.Query().Get("state") | ||
if code == "" || state == "" || state != session.State { | ||
http.Error(w, "Invalid request", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Exchange authorization code for token | ||
token, err := oauthConfig.Exchange( | ||
context.Background(), | ||
code, | ||
oauth2.VerifierOption(session.CodeVerifier), | ||
) | ||
if err != nil { | ||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Update session with token | ||
session.Token = token | ||
sessions[cookie.Value] = session | ||
|
||
// Fetch user info | ||
client := oauthConfig.Client(context.Background(), token) | ||
userInfoURL := fmt.Sprintf("https://%s.projects.oryapis.com/userinfo", projectSlug) | ||
resp, err := client.Get(userInfoURL) | ||
if err != nil { | ||
http.Error(w, "Failed to fetch user info: "+err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
defer resp.Body.Close() | ||
|
||
// Parse user info response | ||
var userInfo map[string]interface{} | ||
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { | ||
http.Error(w, "Failed to parse user info: "+err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Update session with user info | ||
session.UserInfo = userInfo | ||
sessions[cookie.Value] = session | ||
|
||
// Redirect to home page | ||
http.Redirect(w, r, "/", http.StatusSeeOther) | ||
} |
53 changes: 53 additions & 0 deletions
53
docs/oauth2-oidc/get-started/_common/code-examples/go/handlers/login.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Login handler | ||
func handleLogin(w http.ResponseWriter, r *http.Request) { | ||
// Generate random state parameter | ||
state, err := generateRandomString(32) | ||
if err != nil { | ||
http.Error(w, "Failed to generate state parameter", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Generate code verifier for PKCE | ||
codeVerifier := oauth2.GenerateVerifier() | ||
|
||
// Create a new session | ||
sessionID, err := generateRandomString(32) | ||
if err != nil { | ||
http.Error(w, "Failed to generate session ID", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Store session | ||
sessions[sessionID] = Session{ | ||
State: state, | ||
CodeVerifier: codeVerifier, | ||
} | ||
|
||
// Set session cookie | ||
http.SetCookie(w, &http.Cookie{ | ||
Name: "session_id", | ||
Value: sessionID, | ||
Path: "/", | ||
HttpOnly: true, | ||
Secure: r.TLS != nil, | ||
MaxAge: int(24 * time.Hour.Seconds()), | ||
}) | ||
|
||
// Generate authorization URL with PKCE challenge | ||
authURL := oauthConfig.AuthCodeURL( | ||
state, | ||
oauth2.S256ChallengeOption(codeVerifier), | ||
) | ||
|
||
// Redirect to authorization server | ||
http.Redirect(w, r, authURL, http.StatusSeeOther) | ||
} | ||
|
||
// Generate a random string for state parameter | ||
func generateRandomString(length int) (string, error) { | ||
b := make([]byte, length) | ||
if _, err := rand.Read(b); err != nil { | ||
return "", err | ||
} | ||
return base64.RawURLEncoding.EncodeToString(b), nil | ||
} |
21 changes: 21 additions & 0 deletions
21
docs/oauth2-oidc/get-started/_common/code-examples/go/handlers/logout.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
func handleLogout(w http.ResponseWriter, r *http.Request) { | ||
// Get session cookie | ||
cookie, err := r.Cookie("session_id") | ||
if err == nil { | ||
// Delete session | ||
delete(sessions, cookie.Value) | ||
} | ||
|
||
// Clear cookie | ||
http.SetCookie(w, &http.Cookie{ | ||
Name: "session_id", | ||
Value: "", | ||
Path: "/", | ||
HttpOnly: true, | ||
Secure: r.TLS != nil, | ||
MaxAge: -1, | ||
}) | ||
|
||
// Redirect to home page | ||
http.Redirect(w, r, "/", http.StatusSeeOther) | ||
} |
23 changes: 23 additions & 0 deletions
23
docs/oauth2-oidc/get-started/_common/code-examples/go/handlers/protected.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Profile handler (protected route) | ||
func handleProfile(w http.ResponseWriter, r *http.Request) { | ||
// Get session cookie | ||
cookie, err := r.Cookie("session_id") | ||
if err != nil { | ||
http.Error(w, "No session found", http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
// Get session from store | ||
session, ok := sessions[cookie.Value] | ||
if !ok { | ||
http.Error(w, "Invalid session", http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
// Return profile data | ||
w.Header().Set("Content-Type", "application/json") | ||
json.NewEncoder(w).Encode(map[string]interface{}{ | ||
"message": "This is protected data from the resource server", | ||
"user": session.UserInfo, | ||
}) | ||
} |
42 changes: 42 additions & 0 deletions
42
docs/oauth2-oidc/get-started/_common/code-examples/go/handlers/unprotected.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
func handleHome(w http.ResponseWriter, r *http.Request) { | ||
cookie, err := r.Cookie("session_id") | ||
var loggedIn bool | ||
var userInfo map[string]interface{} | ||
|
||
if err == nil { | ||
if session, ok := sessions[cookie.Value]; ok && session.Token != nil && session.Token.Valid() { | ||
loggedIn = true | ||
userInfo = session.UserInfo | ||
} | ||
} | ||
|
||
w.Header().Set("Content-Type", "text/html") | ||
if loggedIn { | ||
// Display logged-in page with user info | ||
fmt.Fprintf(w, ` | ||
<html> | ||
<head><title>OAuth2 Test</title></head> | ||
<body> | ||
<h1>Welcome!</h1> | ||
<p>You are logged in.</p> | ||
<h2>User Info:</h2> | ||
<pre>%v</pre> | ||
<p><a href="/profile">View Profile</a></p> | ||
<p><a href="/logout">Logout</a></p> | ||
</body> | ||
</html> | ||
`, userInfo) | ||
} else { | ||
// Display login page | ||
fmt.Fprintf(w, ` | ||
<html> | ||
<head><title>OAuth2 Test</title></head> | ||
<body> | ||
<h1>Welcome</h1> | ||
<p>You are not logged in.</p> | ||
<p><a href="/login">Login</a></p> | ||
</body> | ||
</html> | ||
`) | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
docs/oauth2-oidc/get-started/_common/code-examples/go/middleware/middleware.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// RequireAuth middleware with explicit token refresh | ||
func RequireAuth(next http.HandlerFunc) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
// Get session cookie | ||
cookie, err := r.Cookie("session_id") | ||
if err != nil { | ||
// No session cookie, redirect to login | ||
http.Redirect(w, r, "/login", http.StatusSeeOther) | ||
return | ||
} | ||
|
||
// Get session from store | ||
session, ok := sessions[cookie.Value] | ||
if !ok { | ||
// Session not found, redirect to login | ||
http.Redirect(w, r, "/login", http.StatusSeeOther) | ||
return | ||
} | ||
|
||
// Check if token is valid | ||
if session.Token == nil || !session.Token.Valid() { | ||
// Token expired, try to refresh it | ||
if session.Token != nil && session.Token.RefreshToken != "" { | ||
// Call the dedicated refresh function | ||
newToken, err := RefreshToken(&session) | ||
if err != nil { | ||
// Refresh failed, redirect to login | ||
log.Printf("Token refresh failed: %v", err) | ||
http.Redirect(w, r, "/login", http.StatusSeeOther) | ||
return | ||
} | ||
|
||
// Update session with new token | ||
session.Token = newToken | ||
sessions[cookie.Value] = session | ||
} else { | ||
// No refresh token, redirect to login | ||
http.Redirect(w, r, "/login", http.StatusSeeOther) | ||
return | ||
} | ||
} | ||
|
||
// Token is valid, proceed to the next handler | ||
next(w, r) | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
docs/oauth2-oidc/get-started/_common/code-examples/go/middleware/refresh-token.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// RefreshToken refreshes an expired token if a refresh token is available | ||
func RefreshToken(session *Session) (*oauth2.Token, error) { | ||
if session.Token == nil || session.Token.RefreshToken == "" { | ||
return nil, fmt.Errorf("no refresh token available") | ||
} | ||
|
||
// Create a TokenSource with the current token | ||
tokenSource := oauthConfig.TokenSource(context.Background(), session.Token) | ||
|
||
// Get a new token (this will use the refresh token if the access token is expired) | ||
newToken, err := tokenSource.Token() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to refresh token: %w", err) | ||
} | ||
|
||
// Log successful token refresh (optional) | ||
log.Println("Token refreshed successfully") | ||
|
||
return newToken, nil | ||
} |
44 changes: 44 additions & 0 deletions
44
docs/oauth2-oidc/get-started/_common/code-examples/go/setup.go
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"golang.org/x/oauth2" | ||
) | ||
|
||
// Configuration | ||
var ( | ||
// Replace these with your own values | ||
clientID = os.Getenv("ORY_CLIENT_ID") | ||
clientSecret = os.Getenv("ORY_CLIENT_SECRET") | ||
projectSlug = os.Getenv("ORY_PROJECT_SLUG") | ||
redirectURL = "http://localhost:8080/callback" | ||
port = "8080" | ||
|
||
// Ory OAuth2 endpoints | ||
oryEndpoint = oauth2.Endpoint{ | ||
AuthURL: fmt.Sprintf("https://%s.projects.oryapis.com/oauth2/auth", projectSlug), | ||
TokenURL: fmt.Sprintf("https://%s.projects.oryapis.com/oauth2/token", projectSlug), | ||
} | ||
|
||
// OAuth2 config | ||
oauthConfig = &oauth2.Config{ | ||
ClientID: clientID, | ||
ClientSecret: clientSecret, | ||
RedirectURL: redirectURL, | ||
Scopes: []string{"openid", "offline_access", "email"}, | ||
Endpoint: oryEndpoint, | ||
} | ||
|
||
// In-memory session store (replace with a proper session store in production) | ||
sessions = make(map[string]Session) | ||
) | ||
|
||
// Session represents user session data | ||
type Session struct { | ||
State string | ||
CodeVerifier string | ||
Token *oauth2.Token | ||
UserInfo map[string]interface{} | ||
} |
21 changes: 21 additions & 0 deletions
21
docs/oauth2-oidc/get-started/_common/code-examples/js/middleware/middleware.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
const requireAuth = (req, res, next) => { | ||
if (!req.session.tokens || !req.session.tokens.access_token) { | ||
// User is not authenticated, redirect to login | ||
return res.redirect("/login") | ||
} | ||
|
||
// Check if the token is expired | ||
if (req.session.tokens.expiresIn && req.session.tokens.expiresIn() <= 0) { | ||
// Token is expired, we need to refresh it if we have a refresh token | ||
if (req.session.tokens.refresh_token) { | ||
// We'll handle refresh in a separate middleware | ||
return refreshToken(req, res, next) | ||
} else { | ||
// No refresh token, redirect to login | ||
return res.redirect("/login") | ||
} | ||
} | ||
|
||
// User is authenticated with a valid token | ||
next() | ||
} |
27 changes: 27 additions & 0 deletions
27
docs/oauth2-oidc/get-started/_common/code-examples/js/middleware/refresh-token.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Middleware to refresh tokens | ||
const refreshToken = async (req, res, next) => { | ||
if (!config) { | ||
return res | ||
.status(500) | ||
.send("Configuration not ready yet. Please try again in a moment.") | ||
} | ||
|
||
try { | ||
// Use the refresh token to get new tokens | ||
const tokens = await client.refreshTokenGrant( | ||
config, | ||
req.session.tokens.refresh_token, | ||
) | ||
|
||
// Update the tokens in the session | ||
req.session.tokens = tokens | ||
|
||
// Continue with the request | ||
next() | ||
} catch (error) { | ||
console.error("Token refresh error:", error) | ||
// Clear session and redirect to login | ||
req.session.destroy() | ||
res.redirect("/login") | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
docs/oauth2-oidc/get-started/_common/code-examples/js/routes/callback.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
app.get("/callback", async (req, res) => { | ||
if (!config) { | ||
return res | ||
.status(500) | ||
.send("Configuration not ready yet. Please try again in a moment.") | ||
} | ||
|
||
try { | ||
// Get the current URL | ||
const currentUrl = new URL(req.url, `http://${req.headers.host}`) | ||
|
||
// Exchange code for tokens with PKCE verification | ||
const tokens = await client.authorizationCodeGrant(config, currentUrl, { | ||
pkceCodeVerifier: req.session.codeVerifier, | ||
expectedState: req.session.state, | ||
}) | ||
|
||
// Store tokens in session | ||
req.session.tokens = tokens | ||
|
||
// Redirect to home page | ||
res.redirect("/") | ||
} catch (error) { | ||
console.error("Callback error:", error) | ||
res.status(500).send(`Authentication failed: ${error.message}`) | ||
} | ||
}) |
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. Probably handle the index route here? |
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
After finishing the flow, I end up at a 404 page because the index page does not exist
