# 🔒 Authorization

All Alby API endpoints are secured by [OAuth 2.0](https://oauth.net/2/). 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.

{% hint style="info" %}
If you want to programmatically access your own Alby account, [create an access token](https://getalby.com/developer/access_tokens/new) and use it as an Authorization header when making API calls:\
\
`Authorization: Bearer YOUR_ACCESS_TOKEN`
{% endhint %}

### Get OAuth client credentials

Login to your account on getalby.com and create a new API client here:&#x20;

{% embed url="<https://getalby.com/oauth_clients>" %}

{% hint style="info" %}
Set `Application URL` and `Callback URL` to `http://localhost:YOUR_PORT_NUMBER` for local development. You can provide a placeholder image if you don't have one for your app yet.
{% endhint %}

{% hint style="warning" %}
If you are hosting your application, make sure the domain you enter in the callback URL field *exactly* matches the domain you are hosting your application.
{% endhint %}

**Endpoints**

* Authorization URI `https://getalby.com/oauth`&#x20;
* Token URI `https://api.getalby.com/oauth/token`

### Redirect your users

Redirect your users to the following URL:

`https://getalby.com/oauth?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_CALLBACK_URL&scope=<scope>`

{% hint style="info" %}
To redirect the user to a specific page or endpoint in your app, instead of passing your domain as the callback URL (e.g. <http://localhost:8080>), include a path such as <http://localhost:8080/auth/callback>
{% endhint %}

#### Parameters

* `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.
* `scope`\
  See [here ](#scopes)for available options.&#x20;
* `state` *(optional)*\
  This parameter will be returned to the callback URI.

{% hint style="info" %}
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.
{% endhint %}

### Scopes

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:&#x20;

```
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.

### Using OAuth Libraries

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.&#x20;

{% tabs %}
{% tab title="node.js" %}
[OAuth 2.0 for Passport.js](https://www.passportjs.org/packages/passport-oauth2/)

```javascript
passport.use(new OAuth2Strategy({
    authorizationURL: 'https://getalby.com/oauth',
    tokenURL: 'https://api.getalby.com/oauth/token',
    clientID: process.env.ALBY_CLIENT_ID,
    clientSecret: process.env.ALBY_CLIENT_SECRET,
    callbackURL: process.env.ALBY_CALLBACK_URL,
},
    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: '/' 
    })
);
```

{% endtab %}
{% endtabs %}

### PKCE

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 should 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:&#x20;

* `code_challenge` with the hash and&#x20;
* `code_challenge_method=S256`.&#x20;

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://getalby.com/oauth?client_id=YOUR_CLIENT_ID&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. This helps to protect against the authorization code being intercepted and used by malicious software to acquire an access token.

#### Javascript example with PKCE

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://getalby.com/oauth";
        const tokenEndpoint = "https://api.getalby.com/oauth/token";
        const clientId = process.env.ALBY_CLIENT_ID;
        const clientSecret = process.env.ALBY_CLIENT_SECRET;
        const callbackUrl = process.env.ALBY_CALLBACK_URL;
	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: callbackUrl,
                    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>

```

### Requesting an access token

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. Replace `redirect_uri` with your app callback url.

```bash
 curl \
 -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \
 -X POST https://api.getalby.com/oauth/token \
 -F 'code=<code>' \
 -F 'grant_type=authorization_code' \
 -F 'redirect_uri=YOUR_CALLBACK_URL'
 -F 'code_verifier=<code_verifier (optional, in case of PKCE)>
```

The response will be:&#x20;

```bash
 {
 "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.**&#x20;

The access token should be used as an Authorization header when making subsequent API calls\
`Authorization: Bearer $access_token`

### Refreshing a token

{% hint style="warning" %}
A refresh token can only be refreshed once (returning a new request token and access token). Make sure to avoid race conditions (e.g. two requests to refresh a token in parallel) as subsequent requests will return an invalid\_grant error. The [Alby JS SDK](https://github.com/getAlby/js-sdk) handles this automatically for you. If you're having trouble with a custom implementation, don't hesitate to reach out.
{% endhint %}

Use the refresh token to obtain a new access token / refresh token.

{% tabs %}
{% tab title="curl" %}

```bash
curl \
 -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \
 -X POST https://api.getalby.com/oauth/token \
 -F 'refresh_token=<refresh_token>' \
 -F 'grant_type=refresh_token'h
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
await axios.post('https://api.getalby.com/oauth/token', {
    refresh_token: user.refresh_token,
    grant_type: "refresh_token",
}, { 
    auth: { 
        username: process.env.ALBY_CLIENT_ID, 
        password: process.env.ALBY_CLIENT_SECRET,
    },
    headers: {
        "Content-Type": "multipart/form-data",
    }
});
```

{% endtab %}
{% endtabs %}

#### ✅ Successful response

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  access_token: '...',
  expires_in: 7200,
  refresh_token: '...',
  scope: 'account:read invoices:read',
  token_type: 'Bearer'
}
```

#### ❌ Error

Requests using expired or invalid access tokens will result in an error like:

<pre><code><strong>HTTP/1.1 401 Unauthorized
</strong>Content-Type: application/json

{
    "error": "expired access token",
    "status": 401
}
</code></pre>

## Debugging with token introspection

You can use an OAuth token to query information about its client id, its redirect uri and its scopes:<br>

```
curl https://api.getalby.com/oauth/token/introspect
-H Authorization:<TOKEN>

Response:

{
    "client_id": "F1kfpr0vi4",
    "redirect_uri": "http://localhost:8080",
    "scopes": {
        "invoices:read": "Read your invoice history, get realtime updates on invoices."
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://guides.getalby.com/developer-guide/developer-guide/alby-wallet-api/reference/authorization.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
