<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/_style/default.xsl" type="text/xsl"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
<generator><![CDATA[Typlog (https://typlog.com/)]]></generator><pubDate>Sat, 08 Aug 2026 15:20:07 +0000</pubDate><atom:link href="https://pubsubhubbub.appspot.com/" rel="hub"/><atom:link href="https://blog.authlib.org/feed.xml" rel="self" type="application/rss+xml"/>
<title><![CDATA[Authlib]]></title><description><![CDATA[Blog of Authlib, the ultimate Python library in building OAuth and OpenID Connect servers.]]></description><link>https://blog.authlib.org/</link><copyright><![CDATA[Copyright 2017 Authlib]]></copyright><image><url>https://i.typlog.com/authlib/8310835341_694759.png?x-oss-process=style/sl</url><title><![CDATA[Authlib]]></title><link>https://blog.authlib.org/</link></image><item><title><![CDATA[Authlib release version 1.7 and get support from the NLNet foundation]]></title><guid>https://blog.authlib.org/2026/authlib-release-version-1-7-and-get-support-from-the-nlnet-foundation</guid><link>https://blog.authlib.org/2026/authlib-release-version-1-7-and-get-support-from-the-nlnet-foundation</link><pubDate>Mon, 04 May 2026 14:16:16 +0000</pubDate><content:encoded><![CDATA[<h2>Authlib version 1.7</h2>
<p>We recently released <a href="https://github.com/authlib/authlib/releases/tag/v1.7.0">version 1.7</a> of Authlib, which includes:</p>
<ul>
<li>migration to <a href="https://jose.authlib.org">joserfc</a> as the replacement for the <code>authlib.jose</code> module. joserfc is based on authlib.jose but is a more modern library. The API have slightly evolved, and it includes new things, like type hints for instance. Read the <a href="https://docs.authlib.org/en/stable/upgrades/jose.html">authlib joserfc migration instructions</a> for more details.</li>
<li>support for <a href="https://docs.authlib.org/en/stable/oauth2/specs/rpinitiated.html">OpenID Connect RP-Initiated Logout</a>. This specification details how client applications can close user sessions at the Identity Provider. Combined with the coming <a href="https://github.com/authlib/authlib/pull/874">OpenID Connect Back-Channel Logout</a> (which does the opposite) it allows for centralized log-out among an entire application ecosystem.</li>
<li>and of course, tons of bugfixes and polishing.</li>
</ul>
<h2>NLNet</h2>
<p>In december 2025 we were accepted in the <a href="https://nlnet.nl/project/Authlib">NLNet NGI0 grant</a>. We want to thank warmfuly the NLNet Foundation for supporting us maintaining and developing Authlib. The joserfc migration as well as the OIDC RPinitiated implementation were the two first tasks we commited to achieve.</p>
<div class="photo"><figure><img src="https://i.typlog.com/authlib/8222096264_183019.png" alt="alt text" /></figure></div><p>The next steps that we will tackle as part of this grant are:</p>
<ul>
<li>bug triaging and fixing. We know some tickets are waiting since a long time, and we plan to spend time on them.</li>
<li>Type hints</li>
<li>Async support</li>
<li>FastAPI support</li>
<li><a href="https://openid.net/specs/openid-connect-backchannel-1_0.html">OpenID Connect Back-Channel Logout</a></li>
<li>OpenID Foundation Certification for the client-side, with <a href="https://github.com/authlib/auth-playground">auth-playground</a></li>
<li>OpenID Foundation Certification for the server-side, by resurrecting and cleaning-up <a href="https://github.com/authlib/example-oauth2-server">example-oauth2-server</a></li>
<li>Security Audit with <a href="https://www.radicallyopensecurity.com">Radically Open Security</a></li>
</ul>
<p>Stay tuned!</p>
]]></content:encoded><dc:creator><![CDATA[Éloi Rivard]]></dc:creator></item><item><title><![CDATA[HMAC-SHA256 for OAuth 1.0]]></title><guid>https://blog.authlib.org/2023/oauth1-hmac-sha256</guid><link>https://blog.authlib.org/2023/oauth1-hmac-sha256</link><description><![CDATA[Implement HMAC-SHA256 signature method for OAuth 1.0 client]]></description><pubDate>Tue, 19 Sep 2023 13:17:29 +0000</pubDate><content:encoded><![CDATA[<p>The default supported signature methods for OAuth 1.0 are <code>HMAC-SHA1</code>, <code>PLAINTEXT</code>, and <code>RSA-SHA1</code>. It's important to note that OAuth 1 is a protocol, not a framework, and there are currently no updates for the OAuth 1 RFC.</p>
<p>However, some companies have chosen to implement alternative signature methods for OAuth 1.0, such as <code>HMAC-SHA256</code>. While our documentation provides an example of how to create a server using this method, we currently lack documentation for the client-side implementation. Nevertheless, implementing the <code>HMAC-SHA256</code> signature method on the client side is a relatively straightforward task.</p>
<p>Let's take a look at the code in <code>authlib.oauth1.rfc5849.signature</code>. The <code>HMAC-SHA1</code> signature method is defined as follows:</p>
<div class="block-code" data-language="python"><pre><code>def hmac_sha1_signature(base_string, client_secret, token_secret):
    text = base_string
    key = escape(client_secret or '')
    key += '&amp;'
    key += escape(token_secret or '')
    signature = hmac.new(to_bytes(key), to_bytes(text), hashlib.sha1)
    sig = binascii.b2a_base64(signature.digest())[:-1]
    return to_unicode(sig)

def sign_hmac_sha1(client, request):
    base_string = generate_signature_base_string(request)
    return hmac_sha1_signature(base_string, client.client_secret, client.token_secret)</code></pre></div>
<p>To implement the <code>HMAC-SHA256</code> signature method, it's quite straightforward. We can essentially copy the <code>HMAC-SHA1</code> signature method and make one key change, replacing the <code>SHA1</code> hash method with <code>SHA256</code>:</p>
<div class="block-code" data-language="python" data-highlight="6"><pre><code>def hmac_sha256_signature(base_string, client_secret, token_secret):
    text = base_string
    key = escape(client_secret or '')
    key += '&amp;'
    key += escape(token_secret or '')
    signature = hmac.new(to_bytes(key), to_bytes(text), hashlib.sha256)
    sig = binascii.b2a_base64(signature.digest())[:-1]
    return to_unicode(sig)

def sign_hmac_sha256(client, request):
    base_string = generate_signature_base_string(request)
    return hmac_sha256_signature(base_string, client.client_secret, client.token_secret)</code></pre></div>
<p>By making this modification, you'll have an <code>HMAC-SHA256</code> signature method that mirrors the structure of the <code>HMAC-SHA1</code> method but uses <code>SHA256</code> for hashing.</p>
<p>And finally, you need to register the signature method:</p>
<div class="block-code" data-language="python"><pre><code>from authlib.oauth1 import ClientAuth

ClientAuth.register_signature_method(&quot;HMAC-SHA256&quot;, sign_hmac_sha256)</code></pre></div>
<p>Once you've registered the <code>HMAC-SHA256</code> signature method, you can use it in all your OAuth 1.0 clients.</p>
]]></content:encoded></item><item><title><![CDATA[Generating EC keys with OpenSSL]]></title><guid>https://blog.authlib.org/2023/openssl-ec-keys</guid><link>https://blog.authlib.org/2023/openssl-ec-keys</link><description><![CDATA[Tips on how to generate EC keys with openssl command line tool.]]></description><pubDate>Sun, 19 Feb 2023 06:25:05 +0000</pubDate><content:encoded><![CDATA[<p>When creating a JWT (JSON Web Token), there are many algorithms for signing the signature. For digital signatures using the ECDSA algorithm, you need an EC key to sign the signature. Here are the algorithms defined by RFC7518 section 3.4 that MUST use an EC key:</p>
<ul>
<li><strong>ES256</strong>: ECDSA using P-256 and SHA-256</li>
<li><strong>ES384</strong>: ECDSA using P-384 and SHA-384</li>
<li><strong>ES512</strong>: ECDSA using P-521 and SHA-512</li>
</ul>
<p>It is very easy to generate an EC key using <code>openssl</code>. But if you are not familiar with <code>openssl</code>, here are some commands that you can just copy and use. I assume you have <code>openssl</code> installed.</p>
<section class="admonition note">
<p class="admonition-title">Note</p>
<p>You can use <code>joserfc</code> to generate EC keys: <a href="https://jose.authlib.org/en/dev/recipes/openssl/">https://jose.authlib.org/en/dev/recipes/openssl/</a></p>
</section>
<h2>EC key with crv P-256</h2>
<p>This key can be used for the <code>alg: ES256</code>, the commands below will generate the private and public keys:</p>
<div class="block-code"><pre><code># generate a private key
openssl ecparam -name prime256v1 -genkey -noout -out ec-p256-private.pem

# extract the public key
openssl ec -in ec-p256-private.pem -pubout -out ec-p256-public.pem</code></pre></div>
<p><strong>Note:</strong> OpenSSL encourages using <code>prime256v1</code> instead of <code>secp256r1</code>.</p>
<h2>EC key with crv P-384</h2>
<p>This key can be used for <code>alg: ES384</code>:</p>
<div class="block-code"><pre><code># generate a private key
openssl ecparam -name secp384r1 -genkey -noout -out ec-p384-private.pem

# extract the public key
openssl ec -in ec-p384-private.pem -pubout -out ec-p384-public.pem</code></pre></div>
<h2>EC key with crv P-512</h2>
<p>This key can be used for <code>alg: ES512</code>:</p>
<div class="block-code"><pre><code># generate a private key
openssl ecparam -name secp521r1 -genkey -noout -out ec-p512-private.pem

# extract the public key
openssl ec -in ec-p512-private.pem -pubout -out ec-p512-public.pem</code></pre></div>
<p><strong>Note:</strong> It is <code>secp521r1</code>, not <code>secp512r1</code>. But the <code>&quot;crv&quot;</code> value in EC Key is <code>&quot;P-512&quot;</code>.</p>
<h2>EC key with crv secp256k1</h2>
<p>This key is used for <strong>ECDSA Signature with secp256k1 Curve</strong> defined by RFC8812.</p>
<ul>
<li><strong>ES256K</strong>: ECDSA using secp256k1 and SHA-256</li>
</ul>
<div class="block-code"><pre><code># generate a private key
openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-private.pem

# extract the public key
openssl ec -in ec-secp256k1-private.pem -pubout -out ec-secp256k1-public.pem</code></pre></div>
]]></content:encoded></item><item><title><![CDATA[Google login for FastAPI]]></title><guid>https://blog.authlib.org/2020/fastapi-google-login</guid><link>https://blog.authlib.org/2020/fastapi-google-login</link><description><![CDATA[An example of how to implement OpenID Connect clients in FastAPI.]]></description><pubDate>Sat, 30 May 2020 12:12:53 +0000</pubDate><content:encoded><![CDATA[<p><a href="https://fastapi.tiangolo.com/">FastAPI</a> is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.</p>
<p>It is created on top of <a href="https://www.starlette.io/">Starlette</a>. A FastAPI app is basically a Starlette app, that is why you can just use <a href="https://docs.authlib.org/en/latest/client/starlette.html">Authlib Starlette</a> integration to create OAuth clients for FastAPI.</p>
<p>We have a post on <a href="https://blog.authlib.org/2020/fastapi-twitter-login">How to create a Twitter login for FastAPI</a>, in this post we will use Google as an example.</p>
<h2>Create OAuth client</h2>
<p>A typical OAuth client for Starlette or FastAPI:</p>
<div class="block-code" data-language="python"><pre><code>from authlib.integrations.starlette_client import OAuth
from starlette.config import Config

config = Config('.env')  # read config from .env file
oauth = OAuth(config)
oauth.register(
    name='google',
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={
        'scope': 'openid email profile'
    }
)</code></pre></div>
<p>We don't need to add <code>client_id</code> and <code>client_secret</code> here, because they are in <code>.env</code> file. You are not supposed to hard code them in the code in real products.</p>
<p>This configuration is very different than Twitter. Since Google has an OpenID discovery endpoint, we can use this URL for <code>server_metadata_url</code>. Authlib will fetch this <code>server_metadata_url</code> to configure the OAuth client for you.</p>
<h2>Implement login route</h2>
<p>First, create a FastAPI application:</p>
<div class="block-code" data-language="python"><pre><code>from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware

app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=&quot;secret-string&quot;)</code></pre></div>
<p>We need this <code>SessionMiddleware</code>, because Authlib will use <code>request.session</code> to store temporary codes and states.</p>
<p>Next, the <code>/login</code> route will redirect us to Google website to grant access:</p>
<div class="block-code" data-language="python"><pre><code>@app.route('/login')
async def login(request: Request):
    # absolute url for callback
    # we will define it below
    redirect_uri = request.url_for('auth')
    return await oauth.google.authorize_redirect(request, redirect_uri)</code></pre></div>
<p>The above code will redirect you to Google account website.</p>
<h2>Handle authentication callback</h2>
<p>When you grant access from Google website, Google will redirect back to your given <code>redirect_uri</code>, which is <code>request.url_for('auth')</code>:</p>
<div class="block-code" data-language="python"><pre><code>@app.route('/auth')
async def auth(request: Request):
    token = await oauth.google.authorize_access_token(request)
    # &lt;=0.15
    # user = await oauth.google.parse_id_token(request, token)
    user = token['userinfo']
    return user</code></pre></div>
<p>The above code will obtain a token which contains <code>access_token</code> and <code>id_token</code>. An <code>id_token</code> contains user info, we just need to parse it to get the login user's information.</p>
<h2>Hint</h2>
<p>You can check the full example: <a href="https://github.com/authlib/demo-oauth-client/tree/master/fastapi-google-login">https://github.com/authlib/demo-oauth-client/tree/master/fastapi-google-login</a>.</p>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Create Twitter login for FastAPI]]></title><guid>https://blog.authlib.org/2020/fastapi-twitter-login</guid><link>https://blog.authlib.org/2020/fastapi-twitter-login</link><description><![CDATA[An example of how to implement OAuth 1.0 clients in FastAPI.]]></description><pubDate>Wed, 06 May 2020 08:34:30 +0000</pubDate><content:encoded><![CDATA[<p><a href="https://fastapi.tiangolo.com/">FastAPI</a> is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.</p>
<p>It is created on top of <a href="https://www.starlette.io/">Starlette</a>. A FastAPI app is basically a Starlette app, that is why you can just use <a href="https://docs.authlib.org/en/latest/client/starlette.html">Authlib Starlette</a> integration to create OAuth clients for FastAPI.</p>
<h2>Create OAuth client</h2>
<p>A typical OAuth client for Starlette or FastAPI:</p>
<div class="block-code" data-language="python"><pre><code>from authlib.integrations.starlette_client import OAuth
from starlette.config import Config

config = Config('.env')  # read config from .env file
oauth = OAuth(config)</code></pre></div>
<p>We will create a twitter login example for FastAPI. Like all <a href="https://docs.authlib.org/en/latest/client/frameworks.html#log-in-with-oauth-1-0">web frameworks integrations in Authlib</a>, we need to register a remote:</p>
<div class="block-code" data-language="python"><pre><code>oauth.register(
    name='twitter',
    api_base_url='https://api.twitter.com/1.1/',
    request_token_url='https://api.twitter.com/oauth/request_token',
    access_token_url='https://api.twitter.com/oauth/access_token',
    authorize_url='https://api.twitter.com/oauth/authenticate',
)</code></pre></div>
<p>We don't need to add <code>client_id</code> and <code>client_secret</code> here, because they are in <code>.env</code> file. You are not supposed to hard code them in the code in real products.</p>
<h2>Implement login route</h2>
<p>First, create a FastAPI application:</p>
<div class="block-code" data-language="python"><pre><code>from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware

app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=&quot;secret-string&quot;)</code></pre></div>
<p>We need this <code>SessionMiddleware</code>, because Authlib will use <code>request.session</code> to store temporary codes and states.</p>
<p>Next, the <code>/login</code> route will redirect us to Twitter website to grant access:</p>
<div class="block-code" data-language="python"><pre><code>@app.route('/login')
async def login(request: Request):
    # absolute url for callback
    # we will define it below
    redirect_uri = request.url_for('auth')
    return await oauth.twitter.authorize_redirect(request, redirect_uri)</code></pre></div>
<p>The above code will exchange <code>request_token</code> and redirect to Twitter website for you.</p>
<h2>Handle authentication callback</h2>
<p>When you grant access from Twitter website, twitter will redirect back to your given <code>redirect_uri</code>, which is <code>request.url_for('auth')</code>:</p>
<div class="block-code" data-language="python"><pre><code>@app.route('/auth')
async def auth(request: Request):
    token = await oauth.twitter.authorize_access_token(request)
    url = 'account/verify_credentials.json'
    resp = await oauth.twitter.get(
        url, params={'skip_status': True}, token=token)
    user = resp.json()
    return user</code></pre></div>
<p>The above code will exchange an <code>access_token</code>. You can use the <code>token</code> to access users' resources. In the above example, we are requesting the authenticated user's profile information.</p>
<h2>Hint</h2>
<p>You can register a Twitter OAuth Client at <a href="https://developer.twitter.com/en/apps">https://developer.twitter.com/en/apps</a>. Remember to add the full <code>auth</code> url in <strong>Callback URL</strong>.</p>
<p>You can check the full example: <a href="https://github.com/authlib/demo-oauth-client/tree/master/fastapi-twitter-login">https://github.com/authlib/demo-oauth-client/tree/master/fastapi-twitter-login</a>.</p>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Upload to Google Cloud Storage from Browser Directly]]></title><guid>https://blog.authlib.org/2019/upload-to-gcs-from-browser</guid><link>https://blog.authlib.org/2019/upload-to-gcs-from-browser</link><description><![CDATA[A Guide on how to upload your photos, audios and any other files to Google Cloud Storage from your browsers directly.]]></description><pubDate>Tue, 01 Oct 2019 03:01:11 +0000</pubDate><content:encoded><![CDATA[<p>Uploading files is full of pain, especially in Python. A large file uploading will block a request for a long time, which will cause a performance problem. Some framework may even have <a href="https://github.com/pallets/werkzeug/issues/875">performance issue</a> with form parsing for files.</p>
<p>What if we can upload files from browser to cloud storage directly? Wouldn't that be wonderful? There are lots of guides on how to do this with S3, but not Google Cloud Storage.</p>
<p>Google however has provided a document for you to upload objects by HTML forms. The API is called <a href="https://cloud.google.com/storage/docs/xml-api/post-object">POST Object</a> API.</p>
<p>Good luck. I hope you can understand the documentation. If not, here is a little tips on how to do it:</p>
<ol>
<li>We need to get a Google service account, which can be created on <a href="https://console.developers.google.com/permissions/serviceaccounts">Service accounts page</a></li>
<li>We need to create a backend API to generate form fields for uploading</li>
<li>Then use the form fields in your front end, submit the form to Google Cloud Storage</li>
</ol>
<p>The most difficult part is how to generate the form fields. Here is how:</p>
<div class="block-code" data-language="python"><pre><code>import json
import datetime

with open('your-google-credential.json') as f:
    conf = json.load(f)

BUCKET = 'your-bucket'

def create_upload_fields(key, content_type, cache_control, acl='public-read'):
    # step 1. prepare form fields
    fields = [
        {'acl': acl},
        {'key': name},
        {'bucket': bucket},
        {'Content-Type': content_type},
    ]
    # you may add more fields
    if cache_control:
        fields.append({'Cache-Control': cache_control})

    # step 2. prepare policy json
    now = datetime.datetime.utcnow()
    # you may set a different expire time
    expires_at = now + datetime.timedelta(minutes=2)
    expiration = expires_at.strftime('%Y-%m-%dT%H:%M:%SZ')
    conditions = list(fields)
    # you may extend conditions here
    policy_json = {
        'expiration': expiration,
        'conditions': conditions
    }
    
    # step 3. base64 policy
    policy_text = json.dumps(policy_json, separators=(',', ':'))
    policy = base64.b64encode(policy_text.encode('utf-8'))
    
    # step 4. sign policy
    signature = base64.b64encode(sign_policy(policy))
    
    # step 5. return fields
    fields.extend([
        {'GoogleAccessId': conf['client_email']},
        {'policy': policy.decode('utf-8')},
        {'signature': signature.decode('utf-8')},
    ])
    return fields</code></pre></div>
<p>The code above has a <code>sign_policy</code> not implemented. We will define it as another method to make our code more readable. This <code>sign_policy</code> will use RSA to sign, we need to install a third party library to do this. My suggestion is <code>cryptography</code>:</p>
<div class="block-code"><pre><code>pip install cryptography</code></pre></div>
<p>You may encounter troubles with installation, check out the installation guide of <a href="https://cryptography.io/en/latest/installation/">cryptography</a>.</p>
<p>Here is our code for <code>sign_policy</code>:</p>
<div class="block-code" data-language="python"><pre><code>from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key

def sign_policy(policy):
    private_key = load_pem_private_key(
        conf['private_key'].encode('utf-8'),  # conf: check above code
        password=None,
        backend=default_backend(),
    )
    return private_key.sign(policy, padding.PKCS1v15(), hashes.SHA256())</code></pre></div>
<p>This is a <code>RS256</code> sign, you may find it is very hard to understand, that is ok. Crypto is hard, but using the library is not, just use the <code>sign_policy</code> code.</p>
<p>With the form fields created by <code>create_upload_fields</code>, you can create the HTML form like:</p>
<div class="block-code" data-language="html"><pre><code>&lt;form action=&quot;https://storage.googleapis.com&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;
&lt;input type=&quot;text&quot; name=&quot;key&quot; value=&quot;some-key-value&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;bucket&quot; value=&quot;your-bucket&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;Content-Type&quot; value=&quot;image/jpeg&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;GoogleAccessId&quot; value=&quot;1234567890123@developer.gserviceaccount.com&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;acl&quot; value=&quot;bucket-owner-read&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;policy&quot; value=&quot;.....&quot;&gt;
&lt;input type=&quot;hidden&quot; name=&quot;signature&quot; value=&quot;....&quot;&gt;

&lt;input name=&quot;file&quot; type=&quot;file&quot;&gt;
&lt;input type=&quot;submit&quot; value=&quot;Upload&quot;&gt;
&lt;/form&gt;</code></pre></div>
<p>Remember to put <code>&lt;input name=&quot;file&quot; type=&quot;file&quot;&gt;</code> at the end of form.</p>
<p>You can add more fields and conditions to your form, read the <a href="https://cloud.google.com/storage/docs/xml-api/post-object">offical guide</a> to find out what they are.</p>
<hr />
<p>Also, you may be interested in <a href="https://blog.authlib.org/2018/multipart-upload-to-google-cloud-storage">Multipart Upload to Google Cloud Storage with Authlib</a>.</p>
<p>Need a more detailed guide? Patreon me at <a href="https://www.patreon.com/lepture">https://www.patreon.com/lepture</a>, I'll create a video guide on uploading to Google Cloud Storage from a single page application (SPA).</p>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Switch to BSD License]]></title><guid>https://blog.authlib.org/2019/switch-to-bsd-license</guid><link>https://blog.authlib.org/2019/switch-to-bsd-license</link><description><![CDATA[Authlib will be released under BSD license from next release.]]></description><pubDate>Tue, 01 Jan 2019 09:12:11 +0000</pubDate><content:encoded><![CDATA[<p>This is an announcement - <strong>license for Authlib will be switched from APGL to BSD from v0.11</strong>.</p>
<p>The <a href="https://github.com/lepture/authlib/issues/59">license issue</a> has been there for a long time. APGL has prevented many people from using Authlib, it is a time to make a change. Authlib will be licensed under BSD from next release v0.11.</p>
<p>The commercial license is still available. New commercial plans will be listed on the <a href="https://authlib.org/plans">pricing page</a>.</p>
<p>Happy new license, happy new year.</p>
<p>Here is the <a href="https://github.com/lepture/authlib/commit/05613853bde5cf9231a198066cda8099c8eb7818">GitHub commit</a>.</p>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Migrate OAuth Client from Flask-OAuthlib to Authlib]]></title><guid>https://blog.authlib.org/2018/migrate-flask-oauthlib-client-to-authlib</guid><link>https://blog.authlib.org/2018/migrate-flask-oauthlib-client-to-authlib</link><description><![CDATA[A guide on how to migrate OAuth client from Flask-OAuthlib to Authlib, and why.]]></description><pubDate>Mon, 21 May 2018 14:35:56 +0000</pubDate><content:encoded><![CDATA[<p>Flask-OAuthlib is deprecated in favor of Authlib. Here is a guide on how to migrate OAuth client from Flask-OAuthlib to Authlib. If you are new to Flask-OAuthlib, you don't have to read this post, instead, just head over to <a href="https://docs.authlib.org/en/latest/client/frameworks.html">Authlib Documentation on Flask Client</a>.</p>
<h2>Why Authlib</h2>
<p>The OAuth client implementation in Flask-OAuthlib is very bad. I didn't mean the API design, oh, the API methods are quite good, and Authlib shares a similar API. However, Flask-OAuthlib is using the built-in <code>urllib2</code> or <code>urllib</code>, which makes things terrible.</p>
<p>There was once a plan to replace them with <code>requests</code>, but it didn't happen until I made the new Authlib, which is another story. The client part is powered by <code>requests</code> in Authlib, which handles http well and correct.</p>
<h2>Initialize</h2>
<p>We will focus on the differences between Flask-OAuthlib and the Flask integration in Authlib. Although Authlib has Django integration as well.</p>
<p>The <code>oauth</code> registries are similar, but with different parameters:</p>
<div class="block-code" data-language="python"><pre><code>from flask_oauthlib.client import OAuth
oauth = OAuth(app)

twitter = oauth.remote_app('twitter',
    base_url='https://api.twitter.com/1.1/',
    request_token_url='https://api.twitter.com/oauth/request_token',
    access_token_url='https://api.twitter.com/oauth/access_token',
    authorize_url='https://api.twitter.com/oauth/authenticate',
    consumer_key='&lt;your key here&gt;',
    consumer_secret='&lt;your secret here&gt;'
)</code></pre></div>
<div class="block-code" data-language="python"><pre><code>from authlib.integrations.flask_client import OAuth

oauth = OAuth(app)
twitter = oauth.register('twitter',
    client_id='Twitter Consumer Key',
    client_secret='Twitter Consumer Secret',
    request_token_url='https://api.twitter.com/oauth/request_token',
    access_token_url='https://api.twitter.com/oauth/access_token',
    authorize_url='https://api.twitter.com/oauth/authenticate',
    api_base_url='https://api.twitter.com/1.1/',
)</code></pre></div>
<h2>Configuration</h2>
<p>Those parameters in <code>.remote_app</code> (Flask-OAuthlib) and <code>.register</code> (Authlib) can be loaded with configurations too. Get the differences in the official documentations:</p>
<ol>
<li>Flask-OAuthlib <a href="http://flask-oauthlib.readthedocs.io/en/latest/client.html#lazy-configuration">Lazy Configuration</a></li>
<li>Authlib <a href="https://docs.authlib.org/en/latest/client/frameworks.html#configuration">Flask Configuration</a></li>
</ol>
<h2>Methods</h2>
<p>The workflow of an OAuth authorization has two steps both in Flask-OAuthlib and Authlib:</p>
<ol>
<li>redirect to the service's login page</li>
<li>back to our authenticated page</li>
</ol>
<p>In Flask-OAuthlib, it looks like:</p>
<div class="block-code" data-language="python"><pre><code>@app.route('/login')
def login():
    redirect_uri = url_for('authorize', _external=True)
    return oauth.twitter.authorize(callback=redirect_uri)

@app.route('/authorize')
def authorize():
    resp_data = oauth.twitter.authorized_response()
    # do something with response data</code></pre></div>
<p>In Authlib, it looks like:</p>
<div class="block-code" data-language="python"><pre><code>@app.route('/login')
def login():
    redirect_uri = url_for('authorize', _external=True)
    return oauth.twitter.authorize_redirect(redirect_uri)

@app.route('/authorize')
def authorize():
    token = oauth.twitter.authorize_access_token()
    # do something with the token</code></pre></div>
<p>The client parts are very simple, what you need to change is the initialization part and the authorization routes, there is nothing difficult.</p>
<h2>Token</h2>
<p>If you want to access resource with methods like <code>oauth.twitter.get(...)</code>, you will need to make sure there is a ready to use access token. This part is very different between Flask-OAuthlib and Authlib.</p>
<p>In Flask-OAuthlib, it is handled by a decorator:</p>
<div class="block-code" data-language="python"><pre><code>@twitter.tokengetter
def get_twitter_oauth_token():
    token = fetch_from_somewhere()
    return token</code></pre></div>
<p>The <code>token</code> returned by <code>tokengetter</code> can be a tuple or a dict. But in Authlib, it can only be a dict, and Authlib doesn't use a decorator to fetch token, instead, you should pass this function to the registry:</p>
<div class="block-code" data-language="python"><pre><code># register the two methods
oauth.register('twitter',
    client_id='Twitter Consumer Key',
    client_secret='Twitter Consumer Secret',
    request_token_url='https://api.twitter.com/oauth/request_token',
    request_token_params=None,
    access_token_url='https://api.twitter.com/oauth/access_token',
    access_token_params=None,
    refresh_token_url=None,
    authorize_url='https://api.twitter.com/oauth/authenticate',
    api_base_url='https://api.twitter.com/1.1/',
    client_kwargs=None,
    # NOTICE HERE
    fetch_token=fetch_twitter_token,
    save_request_token=save_request_token,
    fetch_request_token=fetch_request_token,
)</code></pre></div>
<p>Please note, that Flask-OAuthlib is saving request token in <code>Flask.session</code> which will expose the request token in HTTP transport. In Authlib, you need to save it in other place, like a cache or database. Find more in <a href="https://docs.authlib.org/en/latest/client/frameworks.html#database">Authlib Documentation</a></p>
<h2>Others</h2>
<p>There is a <code>authorized_handler</code> decorator in Flask-OAuthlib which is not recommended anymore. This decorator is not in Authlib. You need to call <code>oauth.twitter.authorize_access_token</code> in the route yourself.</p>
<p>And we have a demo repo: <strong><a href="https://github.com/authlib/demo-oauth-client">https://github.com/authlib/demo-oauth-client</a></strong></p>
<hr />
<ul>
<li>Visit <a href="https://authlib.org/">Authlib Homepage</a></li>
<li>Get more information in <a href="https://docs.authlib.org/">Authlib Documentation</a></li>
<li>Browser <a href="https://github.com/lepture/authlib">Authlib Source Code</a></li>
</ul>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Using Authlib with gspread]]></title><guid>https://blog.authlib.org/2018/authlib-for-gspread</guid><link>https://blog.authlib.org/2018/authlib-for-gspread</link><description><![CDATA[A guide on how to use Authlib in gspread instead of Google oauth2client.]]></description><pubDate>Mon, 21 May 2018 05:09:28 +0000</pubDate><content:encoded><![CDATA[<p>There is a popular python library to communicate with Google Spreadsheets API, which is <a href="https://github.com/burnash/gspread">gspread</a>. Here is a tip on how to use it with Authlib. Just like <a href="https://blog.authlib.org/2018/access-google-analytics-api">Google Analytics API</a> and <a href="https://blog.authlib.org/2018/multipart-upload-to-google-cloud-storage">Google Cloud Storage</a>, Google Spreadsheets is also an <code>AssertionSession</code>.</p>
<h2>Why use Authlib</h2>
<p>But why do you want to use Authlib instead of oauth2client?</p>
<ol>
<li>Authlib <code>AssertionSession</code> and gspread are both using <code>requests</code>, you don't have to use another http library</li>
<li>Authib will automatically refresh access token, while oauth2client in gspread can't. (but oauth2client itself can)</li>
<li>Unlike oauth2client, you know exactly what's going on in Authlib, it is a white box</li>
<li>Oh, <strong>oauth2client is deprecated</strong>.</li>
</ol>
<h2>What is AssertionSession</h2>
<p>Authlib has just released version 0.7. In this version, Authlib has provided a <a href="https://docs.authlib.org/en/latest/client/oauth2.html#assertionsession"><code>AssertionSession</code></a> which is a client implementation of <a href="https://tools.ietf.org/html/rfc7523">RFC7523</a>. That has been said, Google's so called service account is actually <strong>JWT for Authorization Grants</strong>. You can get a bearer token with <code>grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer</code> and a JWT <code>assertion</code>.</p>
<div class="block-code" data-language="http"><pre><code>POST /token.oauth2 HTTP/1.1
Host: authz.example.net
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&amp;assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.eyJpc3Mi[...omitted for brevity...]</code></pre></div>
<p>And this is what <a href="https://docs.authlib.org/en/latest/client/oauth2.html#assertionsession"><code>AssertionSession</code></a> is doing. It will get a valid OAuth token automatically, prepare a requests session for you to use. Yes, it is just a requests session.</p>
<h2>How to use Authlib</h2>
<p>Here is the simple code:</p>
<div class="block-code" data-language="python"><pre><code>import json
from authlib.integrations.requests_client import AssertionSession

def create_assertion_session(conf_file, scopes, subject=None):
    with open(conf_file, 'r') as f:
        conf = json.load(f)

    token_url = conf['token_uri']
    issuer = conf['client_email']
    key = conf['private_key']
    key_id = conf.get('private_key_id')

    header = {'alg': 'RS256'}
    if key_id:
        header['kid'] = key_id

    # Google puts scope in payload
    claims = {'scope': ' '.join(scopes)}
    return AssertionSession(
        grant_type=AssertionSession.JWT_BEARER_GRANT_TYPE,
        token_endpoint=token_url,
        issuer=issuer,
        audience=token_url,
        claims=claims,
        subject=subject,
        key=key,
        header=header,
    )

scopes = [
    'https://spreadsheets.google.com/feeds',
    'https://www.googleapis.com/auth/drive',
]
session = create_assertion_session('your-google-conf.json', scopes)</code></pre></div>
<p>And then use this <code>session</code> in <code>gspread</code>:</p>
<div class="block-code" data-language="python"><pre><code>from gspread import Client

gc = Client(None, session)

wks = gc.open(&quot;Where is the money Lebowski?&quot;).sheet1

wks.update_acell('B2', &quot;it's down there somewhere, let me take another look.&quot;)

# Fetch a cell range
cell_list = wks.range('A1:B7')</code></pre></div>
<p>You can use it directly, there is <strong>no need to call <code>.login()</code> method</strong>.</p>
<p>Find more information on <a href="https://docs.authlib.org/en/latest/client/oauth2.html#assertionsession">Authlib Documentation</a>.</p>
<ul>
<li><a href="https://github.com/lepture/authlib">Authlib Repository</a></li>
<li><a href="https://authlib.org/">Authlib Homepage</a></li>
<li><a href="https://blog.authlib.org/">Authlib Documentation</a></li>
</ul>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Thanks Auth0 for Sponsoring Authlib]]></title><guid>https://blog.authlib.org/2018/thanks-auth0-for-sponsoring-authlib</guid><link>https://blog.authlib.org/2018/thanks-auth0-for-sponsoring-authlib</link><description><![CDATA[A thank you post to Authlib's sponsor Auth0. Other sponsors are welcome.]]></description><pubDate>Tue, 08 May 2018 14:16:38 +0000</pubDate><content:encoded><![CDATA[<p>I'm happy to announce that Authlib got its first sponsor <a href="https://auth0.com/overview?utm_source=GHsponsor&amp;utm_medium=GHsponsor&amp;utm_campaign=authlib&amp;utm_content=auth">Auth0.com</a>. <strong>Thanks Auth0 for sponsoring Authlib</strong>.</p>
<p>Auth0 has sponsored many open source projects, including my previous Flask-OAuthlib. I'm happy to take it as a sponsor for Authlib. I do encourage you to have a try on their service, it is quite easy and in most case it is free. I find it quite useful for single page application.</p>
<p>Here is a total free use case. Consider that you have some single page applications that only allows  your company members to access. You don't have to create a server for it, just use <a href="https://auth0.com/overview?utm_source=GHsponsor&amp;utm_medium=GHsponsor&amp;utm_campaign=authlib&amp;utm_content=auth">Auth0.com</a>, and embed it in your SPA, no one piece of backend code for authentication. You can use a <a href="https://auth0.com/rules/simple-domain-whitelist?utm_source=GHsponsor&amp;utm_medium=GHsponsor&amp;utm_campaign=authlib&amp;utm_content=auth">simple domain whitelist</a> to allow ONLY your company members to access the SPA. Your company members won't exceed 7,000 free active users, right?</p>
<div class="photo"><figure><img src="https://i.typlog.com/authlib/D4/5EXwl7j4Y4Jl8wNxtiZQ.png" alt="auth0 rules" /></figure></div><p>Thanks again for Auth0's sponsoring Authlib. BTW, Auth0 has many <a href="https://auth0.com/opensource?utm_source=GHsponsor&amp;utm_medium=GHsponsor&amp;utm_campaign=authlib&amp;utm_content=auth">open source</a> projects too. And Authlib has a built-in integration with Auth0 via <a href="https://github.com/authlib/loginpass">loginpass</a>.</p>
<hr />
<p>If you have interests in sponsoring <a href="https://authlib.org/">Authlib</a>, please contact <a href="mailto:me@lepture.com">me@lepture.com</a>. I accept three sponsors in total ONLY. Your brand will be listed on:</p>
<ul>
<li>GitHub Readme: <a href="https://github.com/lepture/authlib">https://github.com/lepture/authlib</a></li>
<li>Documentation Sidebar: <a href="https://docs.authlib.org/">https://docs.authlib.org/</a></li>
</ul>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Multipart Upload to Google Cloud Storage with Authlib]]></title><guid>https://blog.authlib.org/2018/multipart-upload-to-google-cloud-storage</guid><link>https://blog.authlib.org/2018/multipart-upload-to-google-cloud-storage</link><description><![CDATA[Uploading files to Google Cloud Storage using requests instead of Google Python Client.]]></description><pubDate>Sat, 05 May 2018 05:12:27 +0000</pubDate><content:encoded><![CDATA[<p>In our last post <em><a href="/2018/access-google-analytics-api">Access Google Analytics API</a></em>, I have said that Google Service Account is no different than a <strong>JWT for Authorization Grants</strong>, what you need to do is fetching the access token with Authlib <a href="https://docs.authlib.org/en/latest/client/oauth2.html#assertionsession"><code>AssertionSession</code></a>. But you don't really need to fetch the token, since <code>AssertionSession</code> will handle it automatically.</p>
<p>First, let's create a requests session with Google service account config file, its <code>scope</code> is <code>https://www.googleapis.com/auth/cloud-platform</code>. Remember to turn Google Storage API on in cloud console.</p>
<div class="block-code" data-language="python"><pre><code>import json
# before v0.13
from authlib.client import AssertionSession
# after v0.13
from authlib.integrations.requests_client import AssertionSession

def create_assertion_session(conf_file, scope, subject=None):
    with open(conf_file, 'r') as f:
        conf = json.load(f)

    token_url = conf['token_uri']
    issuer = conf['client_email']
    key = conf['private_key']
    key_id = conf.get('private_key_id')

    header = {'alg': 'RS256'}
    if key_id:
        header['kid'] = key_id

    # Google puts scope in payload
    claims = {'scope': scope}
    return AssertionSession(
        grant_type=AssertionSession.JWT_BEARER_GRANT_TYPE,
        token_url=token_url,
        issuer=issuer,
        audience=token_url,
        claims=claims,
        subject=subject,
        key=key,
        header=header,
    )

session = create_assertion_session('your-google-conf.json', 'https://www.googleapis.com/auth/cloud-platform')</code></pre></div>
<p>You can always use the <code>GoogleServiceAccount</code> in <a href="https://github.com/authlib/loginpass">loginpass</a> so that you don't need to write the code above. Instead, it can be as simple as:</p>
<div class="block-code" data-language="py"><pre><code>from loginpass.google import GoogleServiceAccount

session = GoogleServiceAccount.from_service_account_file('your-google-conf.json', 'https://www.googleapis.com/auth/cloud-platform')</code></pre></div>
<p>This <code>session</code> is a requests session, which has the same API as requests, such as <code>requests.get</code>, <code>requests.post</code>. Reading the documentation from Google website on <a href="https://cloud.google.com/storage/docs/json_api/v1/how-tos/multipart-upload">JSON API: Performing a Multipart Upload</a>, let's figure out what should we do.</p>
<ol>
<li>figure out what metadata should we send</li>
<li>create a multipart form as the POST payload</li>
</ol>
<p>We will use <a href="https://github.com/requests/toolbelt">requests-toolbelt</a> to create the Multipart Form, which can also sending streaming data. The code will look like:</p>
<div class="block-code" data-language="python"><pre><code>import json
from requests_toolbelt import MultipartEncoder

bucket = 'your-bucket-name'
url = 'https://www.googleapis.com/upload/storage/v1/b/{}/o?uploadType=multipart'.format(bucket)
# file name to be saved in bucket
name = 'foo/bar.jpg'

metadata = {
    'name': name,
    'cacheControl': 'public, max-age=5184000'
}

# obj can be a file / bytes or anything that requests support
obj = open('example.jpg', 'rb')
files = [
    ('file', (name, json.dumps(metadata), 'application/json')),
    ('file', (name, obj, 'image/jpeg')),
]
encoder = MultipartEncoder(files)
headers = {'Content-Type': encoder.content_type}
# use the session created above
resp = session.post(url, data=encoder, headers=headers)</code></pre></div>
<p>The <code>metadata</code> in this example contains a <code>name</code> and <code>cacheControl</code>, between which, the <code>name</code> is required, and you can add more metadata if you want.</p>
<hr />
<p>Checking our guide - <a href="https://blog.authlib.org/2019/upload-to-gcs-from-browser">Upload to Google Cloud Storage from browser directly</a>.</p>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Access Google Analytics API in Python]]></title><guid>https://blog.authlib.org/2018/access-google-analytics-api</guid><link>https://blog.authlib.org/2018/access-google-analytics-api</link><description><![CDATA[Get your Google Analytics data, and build your own graph charts in Python with Authlib.]]></description><pubDate>Wed, 02 May 2018 14:33:59 +0000</pubDate><content:encoded><![CDATA[<p>Google has provided an official library to fetch data from Google Analytics. You can always use the official library if you want, just follow the <a href="https://developers.google.com/analytics/devguides/reporting/core/v4/quickstart/service-py">official guide</a>. But if you want to use Authlib, or if you want to figure out what's going on behind those libraries, you should read this post.</p>
<div class="blockquote"><blockquote><p>Using Authlib to access Google Analytics API is basically using the <strong>requests for human</strong>.</p>
</blockquote></div>
<h2>What is Google Analytics API</h2>
<p>Basically, it is an OAuth POST request to the v4 reporting API:</p>
<div class="block-code" data-language="http"><pre><code>POST /v4/reports:batchGet HTTP/1.1
Host: analyticsreporting.googleapis.com
Content-Type: application/json
Authorization: Bearer string-of-token

{
  &quot;reportRequests&quot;:
  [
    {
      &quot;viewId&quot;: &quot;XXXX&quot;,
      &quot;dateRanges&quot;: [{&quot;startDate&quot;: &quot;2014-11-01&quot;, &quot;endDate&quot;: &quot;2014-11-30&quot;}],
      &quot;metrics&quot;: [{&quot;expression&quot;: &quot;ga:users&quot;}]
    }
  ]
}</code></pre></div>
<p>The main trouble is to get the OAuth token which can be solved by the official python client. You can also use Authlib to get the OAuth access token, which would be better to understand.</p>
<h2>How to Get Token</h2>
<p>Authlib has just released version 0.7. In this version, Authlib has provided a <a href="https://docs.authlib.org/en/latest/client/oauth2.html#assertionsession"><code>AssertionSession</code></a> which is a client implementation of <a href="https://tools.ietf.org/html/rfc7523">RFC7523</a>. That has been said, Google's so called service account is actually <strong>JWT for Authorization Grants</strong>. You can get a bearer token with <code>grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer</code> and a JWT <code>assertion</code>.</p>
<div class="block-code" data-language="http"><pre><code>POST /token.oauth2 HTTP/1.1
Host: authz.example.net
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&amp;assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.eyJpc3Mi[...omitted for brevity...]</code></pre></div>
<p>And this is what <a href="https://docs.authlib.org/en/latest/client/oauth2.html#assertionsession"><code>AssertionSession</code></a> is doing. It will get a valid OAuth token automatically, prepare a requests session for you to use. Yes, it is just a requests session.</p>
<h2>Build an Example</h2>
<p>Let's take a look at what we are going to build. This is a Google Analytics charts in <a href="https://typlog.com/">Typlog</a> dashboard.</p>
<div class="photo"><figure><img src="https://i.typlog.com/authlib/xd/zBnomBez-yc3H5O3lB_Q.png" alt="Typlog Analytics" /></figure></div><p>First, we need to get a Google service account, which can be created on <a href="https://console.developers.google.com/permissions/serviceaccounts">Service accounts page</a>. Then, we can use the provided JSON file to create a <code>AssertionSession</code>.</p>
<div class="block-code" data-language="python"><pre><code>import json
from authlib.integrations.requests_client import AssertionSession

def create_assertion_session(conf_file, scope, subject=None):
    with open(conf_file, 'r') as f:
        conf = json.load(f)

    token_endpoint = conf['token_uri']
    issuer = conf['client_email']
    key = conf['private_key']
    key_id = conf.get('private_key_id')

    header = {'alg': 'RS256'}
    if key_id:
        header['kid'] = key_id

    # Google puts scope in payload
    claims = {'scope': scope}
    return AssertionSession(
        token_endpoint=token_endpoint,
        issuer=issuer,
        claims=claims,
        subject=subject,
        key=key,
        header=header,
    )

session = create_assertion_session('your-google-conf.json', 'https://www.googleapis.com/auth/analytics.readonly')</code></pre></div>
<p>This <code>session</code> is a requests session, which has all the requests methods, like <code>get</code>, <code>post</code>, <code>put</code>, etc. The next thing is to create the POST payload, which would be:</p>
<div class="block-code" data-language="python"><pre><code>report = {
    'viewId': 'XXX',
    'dateRanges': [
        {'startDate': '2018-04-01', 'endDate': '2018-05-01'},
    ],
    'metrics': [
        {'expression': 'ga:pageviews'},
        {'expression': 'ga:sessions'},
        {'expression': 'ga:users'},
    ],
    'dimensions': [
        {'name': 'ga:date'}
    ],
}

BATCH_GET_URL = 'https://analyticsreporting.googleapis.com/v4/reports:batchGet'
resp = session.post(BATCH_GET_URL, json={'reportRequests': [report]})
print(resp.json())</code></pre></div>
<p>You can return the JSON response to the browsers, and the last thing is to build a chart in JS with the response JSON. I'm using <a href="http://www.chartjs.org/">chart.js</a>, you can also use other libraries.</p>
<h2>Build reportRequests</h2>
<p>There is a <code>report</code> in the above section which is the payload to send to Google Analytics API. But how to create such a payload? You can learn it from the <a href="https://developers.google.com/analytics/devguides/reporting/core/v4/basics">official documentation</a>. I will show you some examples of the requests in <a href="https://typlog.com/">Typlog</a>.</p>
<p><strong>A single post analytics data</strong></p>
<div class="block-code" data-language="python"><pre><code>post_filter = {
    'dimensionName': 'ga:pagePath',
    'operator': 'EXACT',
    'expressions': post_page_path
}
site_filter = {
    'dimensionName': 'ga:dimension1',
    'operator': 'EXACT',
    'expressions': site_id
}
base_report = {
    'viewId': view_id,
    'dateRanges': [
        {'startDate': start, 'endDate': end},
    ],
    'dimensionFilterClauses': {
        'filters': [site_filter, post_filter],
        'operator': 'AND'
    }
}
visit_report = {
    'metrics': [
        {'expression': 'ga:pageviews'},
        {'expression': 'ga:sessions'},
    ],
    'dimensions': [
        {'name': 'ga:date'}
    ]
}
referrer_report = {
    'metrics': [
        {'expression': 'ga:pageviews'},
        {'expression': 'ga:sessions'},
    ],
    'dimensions': [
        {'name': 'ga:fullReferrer'}
    ]
}
visit_report.update(base_report)
referrer_report.update(base_report)
reports = [visit_report, referrer_report]

resp = session.post(BATCH_GET_URL, json={'reportRequests': reports})</code></pre></div>
<p>In this example, it has two reports, <code>visit_report</code> is used to create a chart of visits information, and <code>referrer_report</code> is used to create a table of referrer information.</p>
<hr />
<ul>
<li><a href="https://github.com/lepture/authlib">Authlib Repository</a></li>
<li><a href="https://authlib.org/">Authlib Homepage</a></li>
<li><a href="https://docs.authlib.org/">Authlib Documentation</a></li>
</ul>
<p>There is a ready to use <code>GoogleServiceAccount</code> implementation in <a href="https://github.com/authlib/loginpass/blob/master/loginpass/google.py">loginpass</a>.</p>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item><item><title><![CDATA[Hello Authlib]]></title><guid>https://blog.authlib.org/2018/hello-authlib</guid><link>https://blog.authlib.org/2018/hello-authlib</link><description><![CDATA[An introduction of Authlib. And what's is in my mind for Authlib.]]></description><pubDate>Wed, 02 May 2018 08:05:22 +0000</pubDate><content:encoded><![CDATA[<p>The first commit of <a href="https://authlib.org/">Authlib</a> happened on Oct 21 2017, but it really started a very long time ago when Flask-OAuthlib was first introduced. The idea was to create a replacement for Flask-OAuthlib, since it was not well designed (or even implemented wrong).</p>
<p>I find it really hard to continue the development of Flask-OAuthlib, both on code and time. At that moment, OAuthlib, the library it depends was in a very bad status (now it is moved into an org), the APIs it provided are not good enough, or not of my taste. And I barely have much time or enthusiasm to fix the issues.</p>
<p>That's why I created <a href="https://authlib.org/">Authlib</a> with a sustainable idea from the beginning. Get a better idea by reading <a href="https://lepture.com/en/2018/announcement-of-authlib">Announcement of Authlib</a>.</p>
]]></content:encoded><dc:creator><![CDATA[Hsiaoming Yang]]></dc:creator></item></channel></rss>