Skip to content

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/getting-started/ory-network-oauth2.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
id: ory-network-oauth2
title: Perform OAuth2 Authorization Code Grant and Client Credentials Grant
sidebar_label: Try it
sidebar_label: OAuth2
---

# Try common OAuth2 Grants
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new content, this page probably needs to be refreshed or changed? Maybe move it under get started and adjust it to have "Test client credentials grant" and "Test code grant" or something like that where the user, after setting up their project and the client, can check what the flow looks like without code?

# OAuth2 with Ory

[Ory OAuth2 & OpenID Connect](https://www.ory.sh/federated-identity-management/) (based on
[Ory Hydra](https://github.com/ory/hydra)) is available in the Ory Network out of the box. This means that you can use OIDC,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/oauth2-oidc/_static/oauth2overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/oauth2-oidc/_static/ory-oauth2-outcome.png
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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import (
"context"
"encoding/json"
"fmt"
"net/http"

"golang.org/x/oauth2"
)

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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

import (
"crypto/rand"
"encoding/base64"
"net/http"
"time"

"golang.org/x/oauth2"
)

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)
}

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
}
44 changes: 44 additions & 0 deletions docs/oauth2-oidc/get-started/_common/code-examples/go/setup.go
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("OAUTH_CLIENT_ID")
clientSecret = os.Getenv("OAUTH_CLIENT_SECRET")
projectSlug = os.Getenv("ORY_PROJECT_SLUG")
redirectURL = os.Getenv("OAUTH_REDIRECT_URI")
port = "3000"

// 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{}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import session from "express-session"
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
}),
)
app.get("/callback", async (req, res) => {
if (!config) {
throw new Error("Config not found")
}

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("/")
Copy link
Member

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
Screenshot 2025-04-28 at 11 21 11

} catch (error) {
console.error("Callback error:", error)
res.status(500).send(`Authentication failed: ${error.message}`)
}
})

app.listen(3000, () => {
console.log("Server is running on port 3000")
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably handle the index route here?

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
app.get("/login", async (req, res) => {
if (!config) {
throw new Error("Config not found")
}

try {
// Generate PKCE code verifier
const code_verifier = client.randomPKCECodeVerifier()

// Calculate code challenge from verifier
const code_challenge =
await client.calculatePKCECodeChallenge(code_verifier)

// Generate state for CSRF protection
const state = client.randomState()

// Store in session for later verification
req.session.codeVerifier = code_verifier
req.session.state = state

// Build authorization parameters
const parameters = {
redirect_uri: process.env.OAUTH_REDIRECT_URI,
scope: "openid email offline_access",
code_challenge,
code_challenge_method: "S256",
state,
}

// Build authorization URL
const redirectTo = client.buildAuthorizationUrl(config, parameters)

// Redirect user to authorization server
console.log("Redirecting to:", redirectTo.href)
res.redirect(redirectTo.href)
} catch (error) {
console.error("Login error:", error)
res.status(500).send(`Login error: ${error.message}`)
}
})
28 changes: 28 additions & 0 deletions docs/oauth2-oidc/get-started/_common/code-examples/js/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as client from "openid-client"
import express from "express"
const app = express()

// Configure environment variables
const ORY_PROJECT_SLUG = process.env.ORY_PROJECT_SLUG
const OAUTH_CLIENT_ID = process.env.OAUTH_CLIENT_ID
const OAUTH_CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET

let config
;(async () => {
try {
// Create server URL (Ory's issuer URL)
const server = new URL(`https://${ORY_PROJECT_SLUG}.projects.oryapis.com`)

// Use discovery to fetch the server metadata and create a configuration
config = await client.discovery(
server,
OAUTH_CLIENT_ID,
OAUTH_CLIENT_SECRET,
client.ClientSecretBasic(OAUTH_CLIENT_SECRET),
)

console.log("Discovery successful")
} catch (error) {
console.error("Discovery error:", error)
}
})()
Loading
Loading