Authorization
How to use OAuth2 to request access for your application
All Alby API endpoints are secured by OAuth 2.0. Follow these steps in order to allow a user to authorize your application and obtain a token that you can use to access the API on their behalf.
🧑💻 Development
🔴 Production
- Client ID
test_client
- Client Secret
test_secret
- Redirect URI
http://localhost:8080
(⚠️ make sure your application is listening onlocalhost:8080
)
Endpoints
- Authorization URI
https://app.regtest.getalby.com/oauth
- Token URI
https://api.regtest.getalby.com/oauth/token
Please contact us through our e-mail [email protected] and we will provide you with client credentials.
Endpoints
- Authorization URI
https://getalby.com/oauth
- Token URI
https://api.getalby.com/oauth/token
Redirect your users to the following URL:
🧑💻 Development
🔴 Production
https://app.regtest.getalby.com/oauth?client_id=test_client&response_type=code&redirect_uri=http://localhost:8080&scope=<scope>
https://getalby.com/oauth?client_id=test_client&response_type=code&redirect_uri=http://www.example.com/oauth/redirect&scope=<scope>
client_id
The Client ID of your credentials.client_response_type
(code
is used for this type of authentication)redirect_uri
The URI the user should be redirected to after successful authentication. This usually points to your application.state
(optional) This parameter will be returned to the callback URI.
The redirect should be done using a native browser with a URL bar, not with an embedded webview, so users are able to verify that they are being redirected to the correct URL.
Scopes allow you to request fine-grained access permissions for your application. The following scopes are available:
account:read
Request the user's Lightning Address and their keysend information.invoices:create
: Create invoices on a user's behalf.invoices:read
: Read a user's incoming transaction history.transactions:read
: Read a user's outgoing transaction history.balance:read
: Read a user's balance.payments:send
: Send payments on behalf of a user.
You can also request multiple scopes by space-separating and URL encoding them:
...&scope=account:read%20balance:read
If successful, the user will be redirected and your redirect URI will be loaded with a
code
parameter: http://localhost:8080/?code=NWRHNWI3OTITOTFHYS0ZZJNJLTK2YJUTMTVLMZIZOGNIODLK
After users have confirmed your request they will be redirected to the
redirect_uri
you provided. An additional parameter code
will be appended which you will need to exchange for an access token.OAuth 2.0 is a well recognized standard for authentication. Chances are high there is a library available for your application stack that allows you to integrate OAuth 2.0 without much hassle.
node.js
passport.use(new OAuth2Strategy({
authorizationURL: 'https://getalby.com/oauth',
tokenURL: 'https://api.getalby.com/oauth/token',
clientID: process.env.ALBY_CLIENT_ID || "test_client",
clientSecret: process.env.ALBY_CLIENT_SECRET || "test_secret",
callbackURL: process.env.ALBY_CALLBACK_URL || "http://localhost:8080/auth/callback"
},
async function (accessToken, refreshToken, profile, cb) {
// Create a user, save to DB
return cb(null, user);
}));
app.get('/auth/callback',
passport.authenticate('oauth2', { failureRedirect: '/login' }),
function (req, res) {
// User has successfully authenticated, redirect
});
app.get('/login',
passport.authenticate('oauth2', {
scope: ['account:read', 'invoices:read'],
successReturnToOrRedirect: '/'
})
);
Clients that would need to store their
client_secret
in a place that is accessible by end-users, for example pure browser-based or mobile apps, don't need to use a client secret and should instead use the PKCE extension flow to protect against interception of the authorization code. To do this, before redirecting your users, create a random string between 43-128 characters long, then generate the url-safe base64-encoded SHA256 hash of the string. The original random string is known as the code_verifier
, and the hashed version is known as the code_challenge
.In the redirect URL, add 2 extra fields:
code_challenge
with the hash andcode_challenge_method=S256
.
Store the
code_verifier
in local storage, so that you can access it for the 2nd step, after the callback URL has returned. So the request would be:https://app.regtest.getalby.com/oauth?client_id=test_client&code_challenge=<CODE_CHALLENGE>&code_challenge_method=S256&response_type=code&redirect_uri=http://localhost:8080&scope=<scope>
In the 2nd request, you should add
code_verifier
as an extra field, and use an empty string ("") as the client_secret (see below).This helps to protect against the authorization code being intercepted and used by malicious software to acquire an access token.Run this in a browser. You are redirected to localhost, so you need to run a server or you need to re-open the static html file and paste the code callback manually after you’ve been redirected.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Public Client Sample</title>
</head>
<body>
<h1>Public Client Sample</h1>
<button id="startButton">Start OAuth Flow</button>
<div id="result"></div>
<script>
const authorizeEndpoint = "https://app.regtest.getalby.com/oauth";
const tokenEndpoint = "https://api.regtest.getalby.com/oauth/token";
const clientId = "test_client";
const clientSecret = "test_secret";
const scopes = "account:read"
if (window.location.search) {
var args = new URLSearchParams(window.location.search);
var code = args.get("code");
if (code) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var response = xhr.response;
var message;
if (xhr.status == 200) {
message = "Access Token: " + response.access_token;
}
else {
message = "Error: " + response.error_description + " (" + response.error + ")";
}
document.getElementById("result").innerHTML = message;
};
xhr.responseType = 'json';
xhr.open("POST", tokenEndpoint, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader("Authorization", "Basic " + btoa(clientId + ":" + clientSecret));
xhr.send(new URLSearchParams({
code_verifier: window.sessionStorage.getItem("code_verifier"),
grant_type: "authorization_code",
redirect_uri: "http://localhost:8080/",
code: code
}));
}
}
document.getElementById("startButton").onclick = function() {
var codeVerifier = generateRandomString(64);
const challengeMethod = crypto.subtle ? "S256" : "plain"
Promise.resolve()
.then(() => {
if (challengeMethod === 'S256') {
return generateCodeChallenge(codeVerifier)
} else {
return codeVerifier
}
})
.then(function(codeChallenge) {
window.sessionStorage.setItem("code_verifier", codeVerifier);
var redirectUri ="http://localhost:8080/";
var args = new URLSearchParams({
response_type: "code",
client_id: clientId,
scope: scopes,
code_challenge_method: challengeMethod,
code_challenge: codeChallenge,
redirect_uri: redirectUri
});
window.location = authorizeEndpoint + "/?" + args;
});
}
async function generateCodeChallenge(codeVerifier) {
var digest = await crypto.subtle.digest("SHA-256",
new TextEncoder().encode(codeVerifier));
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
function generateRandomString(length) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
if (!crypto.subtle) {
document.writeln('<p>' +
'<b>WARNING:</b> The script will fall back to using plain code challenge as crypto is not available.</p>' +
'<p>Javascript crypto services require that this site is served in a <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts">secure context</a>; ' +
'either from <b>(*.)localhost</b> or via <b>https</b>. </p>' +
'<p> You can add an entry to /etc/hosts like "127.0.0.1 public-test-client.localhost" and reload the site from there, enable SSL using something like <a href="https://letsencrypt.org/">letsencypt</a>, or refer to this <a href="https://stackoverflow.com/questions/46468104/how-to-use-subtlecrypto-in-chrome-window-crypto-subtle-is-undefined">stackoverflow article</a> for more alternatives.</p>' +
'<p>If Javascript crypto is available this message will disappear.</p>')
}
</script>
</body>
</html>
Make a HTTP POST using form-data (
Content-Type: application/x-www-form-urlencoded
), including the previously obtained code
and use the client credentials as basic authentication. In case of a client that cannot use the client secret, use the empty string instead.It is also possible to send the client credentials in the http form parameters, instead of using basic authentication. Use
client_id
and client_secret
as the field names. curl \
-u test_client:test_secret \
-X POST https://api.regtest.getalby.com/oauth/token \
-F 'code=<code>' \
-F 'grant_type=authorization_code' \
-F 'redirect_uri=<callback>'
-F 'code_verifier=<code_verifier (optional, in case of PKCE)>
The response will be:
{
"access_token": "your_access_token",
"expires_in": 7200,
"refresh_token": "your_refresh_token",
"scope": "<scope>",
"token_type": "Bearer"
}
You can then use this
access_token
to make API requests. The access token should be used as an Authorization header when making subsequent API calls
Authorization: Bearer $access_token
Use the refresh token to obtain a new access token / refresh token.
curl
nodejs
curl \
-u test_client:test_secret \
-X POST https://api.regtest.getalby.com/oauth/token \
-F 'refresh_token=<refresh_token>' \
-F 'grant_type=refresh_token'h
await axios.post('https://api.regtest.getalby.com/oauth/token', {
refresh_token: user.refresh_token,
grant_type: "refresh_token",
}, {
auth: {
username: "test_client",
password: "test_secret",
},
headers: {
"Content-Type": "multipart/form-data",
}
});
HTTP/1.1 200 OK
Content-Type: application/json
{
access_token: '...',
expires_in: 7200,
refresh_token: '...',
scope: 'account:read invoices:read',
token_type: 'Bearer'
}
Requests using expired or invalid access tokens will result in an error like:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "expired access token",
"status": 401
}
Last modified 7d ago