Official Allow2 Parental Freedom Service SDK for Ruby -- for web services with user accounts (Rails apps, Sinatra, etc.).
This is a Service SDK -- it runs on your web server, not on a child's device. Following industry standard practice (Stripe, Firebase, Auth0), Allow2 maintains separate Device and Service SDKs. It handles OAuth2 pairing, permission checking, all 3 request types, voice codes, and feedback via the Allow2 Service API.
| Gem | allow2-service |
| Targets | Ruby 3.0+ |
| Dependencies | None (stdlib only: net/http, json, openssl) |
| Language | Ruby |
- Ruby 3.0 or later
- No external gems required (uses stdlib
net/http,json,openssl)
gem install allow2-serviceOr add to your Gemfile:
gem "allow2-service"-
Register your application at developer.allow2.com and note your
client_idandclient_secret. -
Create a
Clientand wire up the OAuth2 flow:
require "allow2_service"
allow2 = Allow2Service::Client.new(
client_id: "YOUR_SERVICE_TOKEN",
client_secret: "YOUR_SERVICE_SECRET",
token_storage: Allow2Service::Storage::FileTokenStorage.new("/var/lib/allow2/tokens.json"),
cache: Allow2Service::Cache::FileCache.new("/tmp/allow2-cache"),
)
# Step 1: Redirect user to Allow2 for pairing
authorize_url = allow2.get_authorize_url(
user_id: current_user_id,
redirect_uri: "https://yourapp.com/allow2/callback",
state: csrf_token,
)
redirect_to authorize_url
# Step 2: Handle the callback
tokens = allow2.exchange_code(
user_id: current_user_id,
code: params[:code],
redirect_uri: "https://yourapp.com/allow2/callback",
)
# Step 3: Check permissions on every request
result = allow2.check(current_user_id, [1, 3]) # Internet + Gaming
if !result.allowed
# Show block page
else
remaining = result.remaining_seconds(1)
# Proceed normally, optionally showing countdown
endThe Service API links a specific user account on your site to exactly one Allow2 child. There is no child selector -- the identity is established at OAuth2 pairing time when the parent selects which child this account belongs to.
This means:
- Each user account on your site maps to one Allow2 child
- The parent performs pairing once per account, selecting the child
- All subsequent permission checks for that account apply to that child automatically
- Parent/admin accounts can be excluded from checking entirely
Redirect the user to Allow2 so their parent can pair the account:
state = SecureRandom.hex(16)
session[:allow2_state] = state
authorize_url = allow2.get_authorize_url(
user_id: current_user_id,
redirect_uri: "https://yourapp.com/callback",
state: state,
)
redirect_to authorize_urlHandle the OAuth2 callback:
raise "Invalid state" unless params[:state] == session[:allow2_state]
tokens = allow2.exchange_code(
user_id: current_user_id,
code: params[:code],
redirect_uri: "https://yourapp.com/callback",
)
# Tokens are stored automatically via the configured token_storageTokens expire. The SDK handles refresh automatically -- when you call check or any other method that requires authentication, the SDK detects expired tokens and refreshes them transparently, persisting the updated tokens via your token storage.
Check permissions on every page load or API request:
# Simple format -- flat array of activity IDs (auto-expanded with log: true)
result = allow2.check(user_id, [1, 3, 8]) # Internet + Gaming + Screen Time
# Full format -- explicit log flags
result = allow2.check(user_id, [
{ "id" => 1, "log" => true }, # Internet
{ "id" => 8, "log" => true }, # Screen Time
], timezone: "Australia/Sydney")
if !result.allowed
puts "Blocked! Day type: #{result.today_day_type.name}"
result.activities.each do |activity|
if activity.banned
puts "#{activity.name} is banned"
elsif !activity.time_block_allowed
puts "#{activity.name} outside allowed hours"
end
end
else
remaining = result.remaining_seconds(1)
# Optionally show countdown in the UI
end# Returns true only if ALL specified activities are allowed
allowed = allow2.allowed?(user_id, [1, 3])The SDK caches check results internally using your configured cache. The default TTL is 60 seconds and can be overridden via the constructor:
allow2 = Allow2Service::Client.new(
client_id: "YOUR_TOKEN",
client_secret: "YOUR_SECRET",
token_storage: storage,
cache: cache,
cache_ttl: 30, # cache for 30 seconds
)Children can request changes directly from your site. There are three types of request, and the philosophy is simple: the child drives the configuration, the parent just approves or denies.
request = allow2.request_more_time(
user_id: user_id,
activity_id: 3, # Gaming
minutes: 30,
message: "Almost done with this level!",
)
puts "Request ID: #{request.request_id}"
# Poll for parent response
status = allow2.get_request_status(request.request_id, request.status_secret)
case status
when "approved" then puts "Approved!"
when "denied" then puts "Request denied."
else puts "Still waiting..."
endrequest = allow2.request_day_type_change(
user_id: user_id,
day_type_id: 2, # Weekend
message: "We have a day off school today.",
)request = allow2.request_ban_lift(
user_id: user_id,
activity_id: 6, # Social Media
message: "I finished all my homework. Can the ban be lifted?",
)Even though the child is on a website (online), the parent may have no internet -- perhaps they are at work with no signal, or their phone is flat. Voice codes let the parent approve a request by reading a short numeric code over the phone or in person.
pair = allow2.generate_voice_challenge(
secret: pairing_secret,
type: Allow2Service::Models::RequestType::MORE_TIME,
activity_id: 3, # Gaming
minutes: 30, # in 5-min increments
)
puts "Challenge code: #{pair.challenge}"
puts "Read this to your parent. Ask them for the response code."valid = allow2.verify_voice_response(
secret: pairing_secret,
challenge: pair.challenge,
response: parent_response_code,
)
if valid
puts "Approved! Extra time granted."
else
puts "Invalid code. Please try again."
endThe codes use HMAC-SHA256 challenge-response, date-bound (expires at midnight). The format is compact enough to read over a phone call: a spaced challenge and a 6-digit response.
Let users submit bug reports and feature requests directly to you, the developer:
# Submit feedback -- returns the discussion ID
discussion_id = allow2.submit_feedback(
user_id: user_id,
category: Allow2Service::Models::FeedbackCategory::BUG,
message: "The block page appears even when I have time remaining.",
)
# Load feedback threads
threads = allow2.load_feedback(user_id)
# Reply to a thread
allow2.reply_to_feedback(user_id, discussion_id, "This happens every Tuesday.")| Module | Purpose |
|---|---|
| Client | Main entry point, orchestrates all operations |
| OAuth2Manager | OAuth2 authorize, code exchange, token refresh |
| PermissionChecker | Permission checks with caching |
| RequestManager | All 3 request types with temp token + status polling |
| VoiceCode | HMAC-SHA256 challenge-response for offline approval |
| FeedbackManager | Submit, load, and reply to feedback threads |
| Model | Purpose |
|---|---|
CheckResult |
Parsed permission check response with per-activity status |
Activity |
Single activity's allowed/blocked state and remaining time |
DayType |
Current and upcoming day type information |
OAuthTokens |
Access token, refresh token, expiry |
RequestResult |
Request ID, status secret, and status with helper methods |
VoiceCodePair |
Challenge and expected response pair |
RequestType |
Constants: MORE_TIME, DAY_TYPE_CHANGE, BAN_LIFT |
FeedbackCategory |
Constants: BUG, FEATURE_REQUEST, NOT_WORKING, OTHER |
| Error | When |
|---|---|
Allow2Error |
Base error for all SDK errors |
ApiError |
HTTP or API-level errors |
TokenExpiredError |
Token refresh failed (re-pairing needed) |
UnpairedError |
No valid tokens for this user (401/403 from API) |
The SDK persists OAuth2 tokens automatically via the token storage you provide at construction. Two built-in adapters are included.
token_storage = Allow2Service::Storage::FileTokenStorage.new("/var/lib/allow2/tokens.json")token_storage = Allow2Service::Storage::MemoryTokenStorage.newImplement the token storage duck type to integrate with your framework:
class ActiveRecordTokenStorage
def store(user_id, tokens)
Allow2Token.upsert({
user_id: user_id,
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
expires_at: tokens.expires_at,
}, unique_by: :user_id)
end
def retrieve(user_id)
record = Allow2Token.find_by(user_id: user_id)
return nil unless record
Allow2Service::Models::OAuthTokens.new(
access_token: record.access_token,
refresh_token: record.refresh_token,
expires_at: record.expires_at,
)
end
def delete(user_id)
Allow2Token.where(user_id: user_id).delete_all
end
def exists?(user_id)
Allow2Token.exists?(user_id: user_id)
end
endThe Allow2 Service API follows a 7-step lifecycle, adapted for server-side web applications:
-
Pairing (one-time) -- OAuth2 flow redirects to Allow2 where the parent selects which child this account belongs to. Tokens are stored per user in your database.
-
Child Identification (automatic) -- one account = one child. The child's identity is established at pairing time and encoded in the OAuth2 tokens. No child selector is needed.
-
Parent Access -- admin or parent accounts on your site are simply excluded from Allow2 checking. No special Allow2 flow is needed.
-
Permission Checks (continuous) -- check on every page load or API request, server-side. Pass
log: trueto record usage. The response includes remaining time, daily limits, time blocks, day types, and bans. -
Warnings & Countdowns -- the server calculates remaining time from the check result. Your frontend displays countdowns and warnings as appropriate (e.g. "5 minutes remaining").
-
Requests -- child requests changes (more time, day type change, ban lift) from your site. The parent approves or denies from their Allow2 app. For parents without internet, voice codes provide offline approval.
-
Feedback -- bug reports and feature requests are sent directly to you, the developer, via the Allow2 feedback system. This gives you a built-in support channel without building one yourself.
"Offline" for a web app sounds contradictory -- but the approval channel can be offline even when the child's browser is online.
The child is on your website (online), but their parent's phone may have no signal. Voice codes solve this:
- Child clicks "Request More Time" on your site
- Your server generates a spaced challenge code
- Child reads the code to their parent (phone call, in person)
- Parent enters it into their Allow2 app (works offline) and reads back the 6-digit response
- Child enters the response on your site
- Your server verifies the HMAC-SHA256 response and grants the time
If the Allow2 API is temporarily unreachable:
- Cache the last check result -- continue enforcing the last known permissions for a short grace period
- Deny by default -- after the grace period, block access to prevent bypass
- Queue requests -- store request attempts and replay them when connectivity resumes
Copyright 2017-2026 Allow2 Pty Ltd. All rights reserved.
See LICENSE for details.