Skip to content

Quickstart ​

Connect an internal app to Fob with OpenID Connect, so your users sign in to it with their Fob account. Any OpenID Connect library works. This guide takes about 10 minutes.

Permission

Needs Manage apps.

Before you begin ​

You need:

  • A Fob organization, such as acme. See Get started.
  • The URL your app's OpenID Connect library redirects back to after sign-in, such as https://foo.acme.com/auth/callback.

1. Add the app in Fob ​

  1. In the console, open Apps > Apps and choose Add app.

  2. Name it, such as foo, and choose a kind:

    KindChoose it for
    Web appAn app with a server, which can keep a client secret
    Browser appA single-page app that runs only in the browser
    Desktop or mobile appA native app that signs in through the system browser
  3. Under Redirect addresses, enter your callback URL. It must use https, except on localhost.

New apps are open to everyone in your organization. To limit an app to some groups, see Assignments.

2. Copy the credentials ​

A web app gets a client secret when you create it.

Warning

Fob shows the client secret only once. Copy it now.

Put the client ID and secret in your app's environment:

sh
FOB_CLIENT_ID=fob_3f9c2a7d41e8b05c6a1d9e27
FOB_CLIENT_SECRET=fob_sk_...

Your library also needs the issuer, which is your organization's URL. You'll find it under Credentials on the app's General tab:

https://acme.fobauth.com

Libraries read everything else from https://acme.fobauth.com/.well-known/openid-configuration.

Tip

If you serve sign-in on a custom domain, such as id.acme.com, choose which URL to use before you launch. The issuer in tokens matches the URL the app uses. See Custom domains.

3. Configure your library ​

These examples read the client ID and secret from the environment and ask for the openid, email and profile scopes. For a browser or desktop app, leave out the secret.

ts
import NextAuth from "next-auth";

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    {
      id: "fob",
      name: "Acme ID",
      type: "oidc",
      issuer: "https://acme.fobauth.com",
      clientId: process.env.FOB_CLIENT_ID,
      clientSecret: process.env.FOB_CLIENT_SECRET,
    },
  ],
});
js
import * as client from "openid-client";

const config = await client.discovery(
  new URL("https://acme.fobauth.com"),
  process.env.FOB_CLIENT_ID,
  process.env.FOB_CLIENT_SECRET,
);

// Starting sign-in: keep codeVerifier and state in the user's session.
const codeVerifier = client.randomPKCECodeVerifier();
const state = client.randomState();
const url = client.buildAuthorizationUrl(config, {
  redirect_uri: "https://foo.acme.com/auth/callback",
  scope: "openid email profile",
  code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
  code_challenge_method: "S256",
  state,
});

// In the callback: currentUrl is the full URL Fob redirected to.
const tokens = await client.authorizationCodeGrant(config, currentUrl, {
  pkceCodeVerifier: codeVerifier,
  expectedState: state,
});
const claims = tokens.claims();
python
import os

from authlib.integrations.flask_client import OAuth
from flask import Flask, redirect, session, url_for

app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET_KEY"]

oauth = OAuth(app)
oauth.register(
    "fob",
    server_metadata_url="https://acme.fobauth.com/.well-known/openid-configuration",
    client_id=os.environ["FOB_CLIENT_ID"],
    client_secret=os.environ["FOB_CLIENT_SECRET"],
    client_kwargs={"scope": "openid email profile", "code_challenge_method": "S256"},
)


@app.route("/login")
def login():
    return oauth.fob.authorize_redirect(url_for("callback", _external=True))


@app.route("/auth/callback")
def callback():
    token = oauth.fob.authorize_access_token()
    session["user"] = token["userinfo"]
    return redirect("/")
go
provider, err := oidc.NewProvider(ctx, "https://acme.fobauth.com")

config := oauth2.Config{
    ClientID:     os.Getenv("FOB_CLIENT_ID"),
    ClientSecret: os.Getenv("FOB_CLIENT_SECRET"),
    Endpoint:     provider.Endpoint(),
    RedirectURL:  "https://foo.acme.com/auth/callback",
    Scopes:       []string{oidc.ScopeOpenID, "email", "profile"},
}
verifier := provider.Verifier(&oidc.Config{ClientID: config.ClientID})

// Starting sign-in: keep state and pkce in the user's session.
pkce := oauth2.GenerateVerifier()
http.Redirect(w, r, config.AuthCodeURL(state, oauth2.S256ChallengeOption(pkce)), http.StatusFound)

// In the callback, after checking the state parameter:
token, err := config.Exchange(ctx, r.URL.Query().Get("code"), oauth2.VerifierOption(pkce))
rawIDToken, _ := token.Extra("id_token").(string)
idToken, err := verifier.Verify(ctx, rawIDToken)

var claims struct {
    Email string `json:"email"`
    Name  string `json:"name"`
}
err = idToken.Claims(&claims)
rust
let metadata = CoreProviderMetadata::discover_async(
    IssuerUrl::new("https://acme.fobauth.com".into())?,
    &http_client,
).await?;

let client = CoreClient::from_provider_metadata(
    metadata,
    ClientId::new(env::var("FOB_CLIENT_ID")?),
    Some(ClientSecret::new(env::var("FOB_CLIENT_SECRET")?)),
)
.set_redirect_uri(RedirectUrl::new("https://foo.acme.com/auth/callback".into())?);

// Starting sign-in: keep csrf_token, nonce and pkce_verifier in the user's session.
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
let (auth_url, csrf_token, nonce) = client
    .authorize_url(
        AuthenticationFlow::<CoreResponseType>::AuthorizationCode,
        CsrfToken::new_random,
        Nonce::new_random,
    )
    .add_scope(Scope::new("email".into()))
    .add_scope(Scope::new("profile".into()))
    .set_pkce_challenge(pkce_challenge)
    .url();

// In the callback, after checking the state parameter against csrf_token:
let response = client
    .exchange_code(AuthorizationCode::new(code))?
    .set_pkce_verifier(pkce_verifier)
    .request_async(&http_client)
    .await?;
let id_token = response.id_token().ok_or("the response has no ID token")?;
let claims = id_token.claims(&client.id_token_verifier(), &nonce)?;
sh
OIDC_ISSUER=https://acme.fobauth.com
OIDC_DISCOVERY_URL=https://acme.fobauth.com/.well-known/openid-configuration
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=

The examples use Auth.js, openid-client, Authlib, go-oidc and openidconnect.

Auth.js redirect URL

Auth.js uses its own callback path, ending in the provider's id. Register https://foo.acme.com/api/auth/callback/fob as the redirect URL.

4. Sign in to test it ​

  1. Open your app and start sign-in. You should land on your organization's Fob sign-in page.
  2. Sign in as a user who is assigned to the app.
  3. Check that your app received the user's email and name claims.

If Fob shows an error page instead of redirecting back, the redirect URL your app sent doesn't exactly match the one you entered in step 1. Check the scheme, port, path and trailing slash.

5. Sign users out ​

To sign a user out of Fob as well as your app, send them to the end-session endpoint with their ID token and a URL to return to:

https://acme.fobauth.com/oauth/end-session?id_token_hint=eyJ...&post_logout_redirect_uri=https%3A%2F%2Ffoo.acme.com%2F

Add the return URL under Sign-out addresses on the app's General tab first.

Next steps ​