OAuth2 Consent — User Consent Management in Authorization Code Flows
In this tutorial, you will learn about OAuth2 Consent. We cover key concepts, practical examples, and best practices to help you master this topic.
OAuth2 consent is the Process where a resource owner explicitly authorizes a client to access their protected resources, choosing which scopes to grant and reviewing the client's identity before approving access.
What You'll Learn
- Consent screen design and user experience
- Dynamic consent for new or additional scopes
- Remembered consent and consent revocation
- Implementing consent in your authorization server
- Security considerations for consent flows
Why It Matters
Consent is the core user protection in OAuth2. A poorly designed consent screen leads to users approving malicious clients or denying legitimate ones. DodaTech's authorization server presents clear, actionable consent screens that reduced accidental deny rates by 60% and improved approval rates for legitimate apps.
Real-World Use
A security dashboard app requests read access to threat reports and write access to remediation actions. The consent screen shows the app name, publisher (verified by DodaTech), requested scopes with plain-language explanations, and the data each scope accesses.
sequenceDiagram
participant User as Resource Owner
participant Browser as Browser
participant Auth as Authorization Server
participant Client as Third-Party App
Client->>Auth: Authorization Request (client_id, redirect_uri, scope)
Auth->>Browser: Redirect to consent page
Browser->>User: Show consent screen: "App X wants access to..."
Note over User,Browser: App Name, Publisher, Scopes (with descriptions)
User->>Browser: Approve (select scopes)
Browser->>Auth: POST consent approval
Auth->>Auth: Store consent grant
Auth->>Browser: Redirect with authorization code
Browser->>Client: Authorization code
Client->>Auth: Exchange code for tokens
Code Examples
Example 1: Consent Screen Endpoint
from flask import Flask, request, render_template, redirect, session
import uuid
app = Flask(__name__)
@app.route('/oauth/authorize', methods=['GET'])
def authorize():
"""Show consent screen to user."""
client_id = request.args.get('client_id')
scope = request.args.get('scope', '').split()
redirect_uri = request.args.get('redirect_uri')
state = request.args.get('state')
client = get_client(client_id)
scope_descriptions = get_scope_descriptions(scope)
user = get_current_user()
# Check for previously remembered consent
previous_consent = get_consent(user.id, client_id)
if previous_consent and set(scope).issubset(set(previous_consent['scopes'])):
# Auto-approve if previously consented
return auto_approve(client, user, scope, redirect_uri, state)
return render_template('consent.html',
client_name=client.name,
client_logo=client.logo_url,
publisher=client.publisher,
scope_descriptions=scope_descriptions,
requested_scopes=scope,
redirect_uri=redirect_uri,
state=state,
client_id=client_id
)
def get_scope_descriptions(scopes):
descriptions = {
'read:threats': 'View threat reports and indicators',
'write:threats': 'Create and update threat reports',
'read:remediation': 'View remediation guides',
'write:remediation': 'Execute remediation actions',
'read:users': 'View user profiles',
'admin': 'Full administrative access'
}
return [{'scope': s, 'description': descriptions.get(s, s)}
for s in scopes]
Example 2: Consent Processing
@app.route('/oauth/consent', methods=['POST'])
def process_consent():
"""Process user consent decision."""
user = get_current_user()
client_id = request.form.get('client_id')
approved_scopes = request.form.getlist('scopes')
remember = 'remember' in request.form
decision = request.form.get('decision')
if decision == 'deny':
return redirect_with_error('access_denied')
# Record consent
consent_record = {
'user_id': user.id,
'client_id': client_id,
'scopes': approved_scopes,
'granted_at': datetime.now(timezone.utc).isoformat(),
'remember': remember
}
save_consent(consent_record)
# Generate authorization code
auth_code = str(uuid.uuid4())
save_auth_code(auth_code, {
'client_id': client_id,
'user_id': user.id,
'scopes': approved_scopes,
'redirect_uri': request.form.get('redirect_uri'),
'expires_at': datetime.now(timezone.utc) + timedelta(minutes=5)
})
# Redirect to client
redirect_uri = request.form.get('redirect_uri')
state = request.form.get('state')
return redirect(f"{redirect_uri}?code={auth_code}&state={state}")
def redirect_with_error(error):
redirect_uri = request.form.get('redirect_uri')
state = request.form.get('state')
return redirect(f"{redirect_uri}?error={error}&state={state}")
Example 3: Consent Revocation
@app.route('/oauth/consent/revoke', methods=['POST'])
def revoke_consent():
"""User revokes previously granted consent."""
user = get_current_user()
client_id = request.form.get('client_id')
scope = request.form.getlist('scopes')
if scope:
# Revoke specific scopes only
revoke_scopes(user.id, client_id, scope)
print(f"Revoked scopes for client {client_id}: {scope}")
else:
# Revoke all consent for this client
revoke_all_consent(user.id, client_id)
print(f"Revoked all consent for client {client_id}")
return render_template('consent-revoked.html',
client_name=get_client(client_id).name)
@app.route('/oauth/consent/manage')
def manage_consent():
"""Show all clients the user has consented to."""
user = get_current_user()
consents = get_user_consents(user.id)
return render_template('consent-manage.html',
consents=[{
'client': get_client(c['client_id']),
'scopes': c['scopes'],
'granted_at': c['granted_at']
} for c in consents]
)
Common Mistakes
1. Burying Scopes in Legalese
Show scopes in plain language with concrete examples of what each scope allows.
2. Not Showing the Client Identity
Display the client name, publisher, website, and logo so users can verify legitimacy.
3. Remembering Consent Without Scope Review
When using remembered consent, still show a notification about what scopes the client is using.
4. No Consent Revocation UI
Users must be able to review and revoke consents from a settings page.
5. Ignoring Incremental Authorization
When a client requests new scopes, prompt for consent again even if previous consent exists.
Practice Questions
- What information should a consent screen display?
- How does remembered consent work?
- What is incremental authorization?
- How do users revoke consent?
- Why is showing the client identity important?
Answers:
- Client name, publisher, logo, requested scopes with plain-language descriptions, and the redirect URI.
- The authorization server stores the user's consent decision and auto-approves future requests with the same or subset of scopes.
- The client starts with a minimal scope set and requests additional scopes later, triggering a new consent prompt.
- Through a consent management page where users can review all granted consents and revoke them individually.
- Attackers create lookalike apps. Verified publisher identity and logos help users distinguish legitimate apps.
Challenge: Build a consent management system with remembered consent, incremental authorization, scope descriptions, and a consent revocation dashboard.
FAQ
What's Next
Implement consent in your {{< ilink "OAuth" "OAuth2 Authorization Server" }}, then explore {{< ilink "OAuth" "OAuth2 Scopes" }} for fine-grained permission design.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro