Add the Login Button
Copy this HTML to add a styled "Login with Locke" button:
content_copy
<a href ="/auth/locke" class ="login-with-locke" >
<img src ="https://locke.id/images/login-with-locke-icon.webp"
alt ="Locke" width ="24" height ="24" >
Login with Locke
</a>
<style>
.login-with-locke {
display : inline-flex;
align-items : center;
gap : 12px;
padding : 14px 28px;
background : #0a91b3 ;
color : white;
border-radius : 8px;
font-family : 'Merriweather' , Georgia, serif;
font-size : 16px;
text-decoration : none;
transition : background 0.2s;
}
.login-with-locke :hover { background : #087a96 ; }
</style>
PQ
Post-Quantum Ready
No changes needed - the ID token automatically includes a pq_sig claim with a Dilithium3 signature. Your backend can optionally verify it for quantum-resistant authentication.
Browser JavaScript (with PKCE)
Complete OAuth flow for single-page apps:
content_copy
async function generatePKCE () {
const verifier = crypto.randomUUID() + crypto.randomUUID();
const hash = await crypto.subtle.digest('SHA-256' , new TextEncoder().encode(verifier));
const challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/\+/g, '-' ).replace(/\//g, '_' ).replace(/=/g, '' );
return { verifier, challenge };
}
async function loginWithLocke () {
const { verifier, challenge } = await generatePKCE ();
const state = crypto.randomUUID();
sessionStorage.setItem('pkce_verifier' , verifier);
sessionStorage.setItem('oauth_state' , state);
const params = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID' ,
redirect_uri: 'https://yourapp.com/callback' ,
response_type: 'code' ,
scope: 'openid profile email' ,
state,
code_challenge: challenge,
code_challenge_method: 'S256'
});
window.location.href = `https://app.locke.id/oauth/consent?${params}` ;
}
async function handleCallback () {
const params = new URLSearchParams(window.location.search);
if (params.get('state' ) !== sessionStorage.getItem('oauth_state' )) {
throw new Error('Invalid state' );
}
const response = await fetch('https://api.locke.id/oauth/token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code' ,
code: params.get('code' ),
redirect_uri: 'https://yourapp.com/callback' ,
client_id: 'YOUR_CLIENT_ID' ,
code_verifier: sessionStorage.getItem('pkce_verifier' )
})
});
const tokens = await response.json();
const user = await fetch('https://api.locke.id/oauth/userinfo' , {
headers: { 'Authorization' : `Bearer ${tokens.access_token}` }
}).then(r => r.json());
console.log('Logged in as:' , user.name);
}
PQ
Post-Quantum Verification (Optional)
The id_token contains a pq_sig claim. To verify it in the browser, you'd need a Dilithium3 WASM library. For most apps, server-side verification is recommended.
Go Backend Integration
Complete OAuth flow with net/http:
content_copy
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"net/http"
"net/url"
)
const (
clientID = "YOUR_CLIENT_ID"
redirectURI = "https://yourapp.com/callback"
authorizeURL = "https://app.locke.id/oauth/consent"
tokenURL = "https://api.locke.id/oauth/token"
userInfoURL = "https://api.locke.id/oauth/userinfo"
)
func generatePKCE () (string , string ) {
bytes := make ([]byte , 64)
rand.Read(bytes)
verifier := base64.RawURLEncoding.EncodeToString(bytes)[:64]
hash := sha256.Sum256([]byte (verifier))
challenge := base64.RawURLEncoding.EncodeToString(hash[:])
return verifier, challenge
}
func handleLogin (w http.ResponseWriter, r *http.Request) {
verifier, challenge := generatePKCE ()
state := base64.RawURLEncoding.EncodeToString(make ([]byte , 32))[:32]
http.SetCookie(w, &http.Cookie{Name: "pkce" , Value: verifier, HttpOnly: true })
http.SetCookie(w, &http.Cookie{Name: "state" , Value: state, HttpOnly: true })
params := url.Values{
"client_id" : {clientID},
"redirect_uri" : {redirectURI},
"response_type" : {"code" },
"scope" : {"openid profile email" },
"state" : {state},
"code_challenge" : {challenge},
"code_challenge_method" : {"S256" },
}
http.Redirect(w, r, authorizeURL+"?" +params.Encode(), http.StatusFound)
}
func handleCallback (w http.ResponseWriter, r *http.Request) {
stateCookie, _ := r.Cookie("state" )
if r.URL.Query().Get("state" ) != stateCookie.Value {
http.Error(w, "Invalid state" , http.StatusBadRequest)
return
}
verifierCookie, _ := r.Cookie("pkce" )
resp, _ := http.PostForm(tokenURL, url.Values{
"grant_type" : {"authorization_code" },
"code" : {r.URL.Query().Get("code" )},
"redirect_uri" : {redirectURI},
"client_id" : {clientID},
"code_verifier" : {verifierCookie.Value},
})
defer resp.Body.Close()
var tokens struct {
AccessToken string `json:"access_token"`
IDToken string `json:"id_token"`
}
json.NewDecoder(resp.Body).Decode(&tokens)
req, _ := http.NewRequest("GET" , userInfoURL, nil )
req.Header.Set("Authorization" , "Bearer " +tokens.AccessToken)
userResp, _ := http.DefaultClient.Do(req)
}
PQ
Post-Quantum Verification with cloudflare/circl
content_copy
import "github.com/cloudflare/circl/sign/dilithium/mode3"
func verifyPQSignature (idToken, pqPubKeyB64 string ) error {
parts := strings.Split(idToken, "." )
payload, _ := base64.RawURLEncoding.DecodeString(parts[1])
var claims struct { PQSig string `json:"pq_sig"` }
json.Unmarshal(payload, &claims)
sig, _ := base64.RawURLEncoding.DecodeString(claims.PQSig)
pubKeyBytes, _ := base64.RawURLEncoding.DecodeString(pqPubKeyB64)
var pubKey mode3.PublicKey
pubKey.Unpack((*[mode3.PublicKeySize]byte )(pubKeyBytes))
message := []byte (parts[0] + "." + parts[1])
if !mode3.Verify(&pubKey, message, sig) {
return errors.New("PQ signature invalid" )
}
return nil
}
Python/Flask Integration
OAuth flow with Flask:
content_copy
import hashlib, secrets, base64, requests
from flask import Flask, redirect, request, session
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
CLIENT_ID = "YOUR_CLIENT_ID"
REDIRECT_URI = "https://yourapp.com/callback"
AUTHORIZE_URL = "https://app.locke.id/oauth/consent"
TOKEN_URL = "https://api.locke.id/oauth/token"
USERINFO_URL = "https://api.locke.id/oauth/userinfo"
def generate_pkce ():
"""Generate PKCE verifier and S256 challenge."""
verifier = secrets.token_urlsafe(64)[:64]
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier.encode()).digest()
).rstrip(b"=" ).decode()
return verifier, challenge
@app.route ("/login" )
def login ():
verifier, challenge = generate_pkce ()
state = secrets.token_urlsafe(32)
session["pkce_verifier" ] = verifier
session["oauth_state" ] = state
params = {
"client_id" : CLIENT_ID,
"redirect_uri" : REDIRECT_URI,
"response_type" : "code" ,
"scope" : "openid profile email" ,
"state" : state,
"code_challenge" : challenge,
"code_challenge_method" : "S256" ,
}
return redirect(f"{AUTHORIZE_URL}?{requests.compat.urlencode(params)}" )
@app.route ("/callback" )
def callback ():
if request.args.get("state" ) != session.get("oauth_state" ):
return "Invalid state" , 400
tokens = requests.post(TOKEN_URL, data={
"grant_type" : "authorization_code" ,
"code" : request.args.get("code" ),
"redirect_uri" : REDIRECT_URI,
"client_id" : CLIENT_ID,
"code_verifier" : session["pkce_verifier" ],
}).json()
user = requests.get(USERINFO_URL, headers={
"Authorization" : f"Bearer {tokens['access_token']}"
}).json()
return f"Welcome, {user.get('name', user['sub'])}!"
PQ
Post-Quantum Verification with liboqs-python
content_copy
import oqs, base64, json
def verify_pq_signature (id_token: str , pq_pub_key_b64: str ) -> bool :
"""Verify Dilithium3 signature in id_token's pq_sig claim."""
parts = id_token.split("." )
payload = json.loads(base64.urlsafe_b64decode(parts[1] + "==" ))
pq_sig = payload.get("pq_sig" )
signature = base64.urlsafe_b64decode(pq_sig + "==" )
public_key = base64.urlsafe_b64decode(pq_pub_key_b64 + "==" )
message = f"{parts[0]}.{parts[1]}" .encode()
verifier = oqs.Signature("Dilithium3" )
return verifier.verify(message, signature, public_key)
Node.js/Express Integration
OAuth flow with Express:
content_copy
const express = require('express' );
const crypto = require('crypto' );
const session = require('express-session' );
const app = express();
app.use(session({ secret: crypto.randomBytes(32).toString('hex' ), resave: false , saveUninitialized: false }));
const CLIENT_ID = 'YOUR_CLIENT_ID' ;
const REDIRECT_URI = 'https://yourapp.com/callback' ;
const AUTHORIZE_URL = 'https://app.locke.id/oauth/consent' ;
const TOKEN_URL = 'https://api.locke.id/oauth/token' ;
const USERINFO_URL = 'https://api.locke.id/oauth/userinfo' ;
function generatePKCE () {
const verifier = crypto.randomBytes(64).toString('base64url' ).slice(0, 64);
const challenge = crypto.createHash('sha256' ).update(verifier).digest('base64url' );
return { verifier, challenge };
}
app.get('/login' , (req, res) => {
const { verifier, challenge } = generatePKCE ();
const state = crypto.randomUUID();
req.session.pkceVerifier = verifier;
req.session.oauthState = state;
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code' ,
scope: 'openid profile email' ,
state,
code_challenge: challenge,
code_challenge_method: 'S256' ,
});
res.redirect(`${AUTHORIZE_URL}?${params}` );
});
app.get('/callback' , async (req, res) => {
if (req.query.state !== req.session.oauthState) {
return res.status(400).send('Invalid state' );
}
const tokens = await fetch(TOKEN_URL, {
method: 'POST' ,
headers: { 'Content-Type' : 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code' ,
code: req.query.code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
code_verifier: req.session.pkceVerifier,
}),
}).then(r => r.json());
const user = await fetch(USERINFO_URL, {
headers: { 'Authorization' : `Bearer ${tokens.access_token}` },
}).then(r => r.json());
res.send(`Welcome, ${user.name || user.sub}!` );
});
app.listen(3000);
PQ
Post-Quantum Verification with liboqs-node
content_copy
const { Signature } = require('liboqs-node' );
async function verifyPQSignature (idToken, pqPubKeyB64) {
const parts = idToken.split('.' );
const payload = JSON.parse(Buffer.from(parts[1], 'base64url' ).toString());
const pqSig = payload.pq_sig;
const signature = Buffer.from(pqSig, 'base64url' );
const publicKey = Buffer.from(pqPubKeyB64, 'base64url' );
const message = Buffer.from(`${parts[0]}.${parts[1]}` );
const sig = new Signature('Dilithium3' );
return sig.verify(message, signature, publicKey);
}