Available

Add sign-in to an app

Sign a person in to your application with OpenID Connect, using the authorization code flow with PKCE, and read who they are.

In this tutorial you sign a person in to an application and read their identity. You use the OpenID Connect authorization code flow with PKCE (RFC 7636), which is the flow every OpenID Connect library implements.

The steps show each request on the wire, so you can see what your library does for you. It takes about ten minutes.

Before you begin

You need:

  • An AuthFI project and its issuer, the project's URL. This tutorial uses https://acme.authfi.io.
  • Permission to add applications in the AuthFI console.
  • curl and openssl, or Node.js 18 or later, or Python 3.

Register your application

  1. In the console, open Integrations > Applications and select Register.
  2. Under What are you connecting?, choose OIDC / OAuth 2.0.
  3. Choose the kind that matches where your code runs:
    • Web application runs on your server and can keep a secret.
    • Single-page app runs in the browser and proves itself with PKCE instead of a secret.
  4. Enter a Name. Under Redirect URIs, enter the URL in your application that AuthFI sends the person back to after they sign in. This tutorial uses https://app.acme.com/callback. Enter one URI per line.
  5. Review the summary and select Register.

The last step shows the application's Client id. A web application also gets a Client secret, shown once: copy it, then tick I have copied the client secret to continue. The same step lists the project's endpoints, under Paste these into the other side.

export CLIENT_ID=<your client ID>

Create a PKCE verifier and challenge

PKCE ties the sign-in to the application that started it. You create a random secret, the verifier, and send only its SHA-256 hash, the challenge. Later you prove you hold the verifier.

VERIFIER=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -hex 16)

Keep the verifier and state in the person's session on your server. You need both when they come back.

Send the person to sign in

Redirect the browser to the project's authorization endpoint.

GET

/oauth/authorize
echo "https://acme.authfi.io/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.acme.com%2Fcallback&scope=openid%20profile%20email&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"
ParameterValue in this tutorialPurpose
response_typecodeAsk for an authorization code
client_idYour client IDIdentifies your application
redirect_urihttps://app.acme.com/callbackMust match a registered redirect URI exactly
scopeopenid profile emailopenid is required. The others add profile and email.
stateA random valueReturned unchanged, so you can match the answer to the ask
code_challengeThe challengeThe PKCE challenge from the previous step
code_challenge_methodS256The only method accepted

AuthFI shows your project's sign-in page. To try the flow by hand, open the URL in a browser and sign in.

Receive the authorization code

After the person signs in, the browser returns to your redirect URI:

https://app.acme.com/callback?code=Xq3v...&state=5f2a...&iss=https%3A%2F%2Facme.authfi.io

Compare state with the value you stored. If they differ, stop: the response does not belong to a sign-in you started.

The code works once and expires after 5 minutes.

export CODE=<the code from the URL>

Exchange the code for tokens

Send the code and the verifier to the token endpoint. The body is form-encoded.

POST

/oauth/token
curl https://acme.authfi.io/oauth/token \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://app.acme.com/callback \
  -d client_id="$CLIENT_ID" \
  -d code_verifier="$VERIFIER"

A web application also sends its client secret, as HTTP Basic authentication or as client_secret in the body.

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "...",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii4uLiJ9...",
  "scope": "openid profile email"
}
TokenUse it toLifetime
id_tokenLearn who signed in15 minutes by default
access_tokenCall APIs on the person's behalf15 minutes by default
refresh_tokenGet new tokens without another sign-in30 days by default

Validate the ID token

The ID token is a signed JWT. Before you trust it, validate it. An OpenID Connect library does this when you configure it with these values:

SettingValue
Issuerhttps://acme.authfi.io
Keyshttps://acme.authfi.io/.well-known/jwks.json
AudienceYour client ID
AlgorithmRS256

The library checks the signature against the project's keys, and checks that iss is your issuer, aud is your client ID and exp is in the future.

The validated token tells you who signed in:

{
  "iss": "https://acme.authfi.io",
  "sub": "[email protected]",
  "aud": "<your client ID>",
  "email": "[email protected]",
  "email_verified": true,
  "name": "Ada Lovelace",
  "groups": ["engineering"],
  "roles": ["developer"],
  "auth_time": 1760000000,
  "iat": 1760000000,
  "exp": 1760000900
}

Check that it works

Call the userinfo endpoint with the access token.

curl https://acme.authfi.io/oauth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"
{
  "sub": "[email protected]",
  "email": "[email protected]",
  "email_verified": true,
  "name": "Ada Lovelace",
  "groups": ["engineering"],
  "roles": ["developer"]
}

If you see the person you signed in as, the flow works end to end.

Troubleshooting

What you seeCauseFix
A 400 error with invalid_request instead of the sign-in pageThe redirect_uri is not registered for the applicationRegister the exact URI, including path and port
A 401 error with invalid_clientThe client_id is not an application in this projectCheck the client ID and the issuer
The browser returns with error=access_deniedThe application is assigned to groups, and the person is in noneOn the application's page, select Assign a group and choose one of their groups
invalid_grant from the token endpointThe code expired, was already used, or the verifier does not matchStart again from Send the person to sign in. Exchange each code once, within 5 minutes.
invalid_client from the token endpointThe client secret is missing or wrongA web application must send its client secret. If it is lost, rotate it on the application's page.

Every error is listed in the endpoint reference.

Next steps