-
-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7e1bcea
commit 81838ca
Showing
10 changed files
with
185 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add KeyCloak provider |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
--- | ||
title: "KeyCloak" | ||
--- | ||
|
||
# Okta | ||
|
||
OAuth 2.0 provider for KeyCloak. | ||
|
||
Also see the [OAuth 2.0 with PKCE](/guides/oauth2-pkce) guide. | ||
|
||
## Initialization | ||
|
||
The realm URL should not include trailing slashes. | ||
|
||
```ts | ||
import { KeyCloak } from "arctic"; | ||
|
||
const realmURL = "https://auth.example.com/realms/myrealm | ||
const keycloak = new KeyCloak(realmURL, clientId, clientSecret, redirectURI); | ||
``` | ||
|
||
## Create authorization URL | ||
|
||
Use `addScopes()` to define scopes. | ||
|
||
```ts | ||
import { generateState, generateCodeVerifier } from "arctic"; | ||
|
||
const state = generateState(); | ||
const codeVerifier = generateCodeVerifier(); | ||
const url = keycloak.createAuthorizationURL(state, codeVerifier); | ||
url.addScopes("openid", "profile"); | ||
``` | ||
|
||
## Validate authorization code | ||
|
||
`validateAuthorizationCode()` will either return an [`OAuth2Tokens`](/reference/main/OAuth2Tokens), or throw one of [`OAuth2RequestError`](/reference/main/OAuth2RequestError), [`ArcticFetchError`](/reference/main/ArcticFetchError), or a standard `Error` (parse errors). Actual values returned by KeyCloak depends on your configuration and version. | ||
|
||
```ts | ||
import { OAuth2RequestError, ArcticFetchError } from "arctic"; | ||
|
||
try { | ||
const tokens = await keycloak.validateAuthorizationCode(code, codeVerifier); | ||
const accessToken = tokens.accessToken(); | ||
const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); | ||
const refreshToken = tokens.refreshToken(); | ||
} catch (e) { | ||
if (e instanceof OAuth2RequestError) { | ||
// Invalid authorization code, credentials, or redirect URI | ||
const code = e.code; | ||
// ... | ||
} | ||
if (e instanceof ArcticFetchError) { | ||
// Failed to call `fetch()` | ||
const cause = e.cause; | ||
// ... | ||
} | ||
// Parse error | ||
} | ||
``` | ||
|
||
## OpenID Connect | ||
|
||
Use OpenID Connect with the `openid` scope to get the user's profile with an ID token or the `userinfo` endpoint. Arctic provides [`decodeIdToken()`](/reference/main/decodeIdToken) for decoding the token's payload. | ||
|
||
```ts | ||
const url = keycloak.createAuthorizationURL(state, codeVerifier); | ||
url.addScopes("openid"); | ||
``` | ||
|
||
```ts | ||
import { decodeIdToken } from "arctic"; | ||
|
||
const tokens = await keycloak.validateAuthorizationCode(code, codeVerifier); | ||
const idToken = tokens.idToken(); | ||
const claims = decodeIdToken(idToken); | ||
``` | ||
|
||
## Refresh access tokens | ||
|
||
Use `refreshAccessToken()` to get a new access token using a refresh token. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()`. | ||
|
||
```ts | ||
import { OAuth2RequestError, ArcticFetchError } from "arctic"; | ||
|
||
try { | ||
const tokens = await keycloak.refreshAccessToken(accessToken); | ||
} catch (e) { | ||
if (e instanceof OAuth2RequestError) { | ||
// Invalid authorization code, credentials, or redirect URI | ||
} | ||
if (e instanceof ArcticFetchError) { | ||
// Failed to call `fetch()` | ||
} | ||
// Parse error | ||
} | ||
``` | ||
|
||
## Revoke tokens | ||
|
||
Use `revokeToken()` to revoke a token. This can throw the same errors as `validateAuthorizationCode()`. | ||
|
||
```ts | ||
try { | ||
await keycloak.revokeToken(token); | ||
} catch (e) { | ||
if (e instanceof OAuth2RequestError) { | ||
// Invalid authorization code, credentials, or redirect URI | ||
} | ||
if (e instanceof ArcticFetchError) { | ||
// Failed to call `fetch()` | ||
} | ||
// Parse error | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { | ||
AuthorizationCodeAuthorizationURL, | ||
AuthorizationCodeTokenRequestContext, | ||
RefreshRequestContext, | ||
TokenRevocationRequestContext | ||
} from "@oslojs/oauth2"; | ||
import { sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; | ||
|
||
import type { OAuth2Tokens } from "../oauth2.js"; | ||
|
||
export class KeyCloak { | ||
private authorizationEndpoint: string; | ||
private tokenEndpoint: string; | ||
private tokenRevocationEndpoint: string; | ||
|
||
private clientId: string; | ||
private clientSecret: string; | ||
private redirectURI: string; | ||
|
||
constructor(realmURL: string, clientId: string, clientSecret: string, redirectURI: string) { | ||
this.authorizationEndpoint = realmURL + "/protocol/openid-connect/auth"; | ||
this.tokenEndpoint = realmURL + "/protocol/openid-connect/token"; | ||
this.tokenRevocationEndpoint = realmURL + "/protocol/openid-connect/revoke"; | ||
this.clientId = clientId; | ||
this.clientSecret = clientSecret; | ||
this.redirectURI = redirectURI; | ||
} | ||
|
||
public createAuthorizationURL( | ||
state: string, | ||
codeVerifier: string | ||
): AuthorizationCodeAuthorizationURL { | ||
const url = new AuthorizationCodeAuthorizationURL(this.authorizationEndpoint, this.clientId); | ||
url.setRedirectURI(this.redirectURI); | ||
url.setState(state); | ||
url.setS256CodeChallenge(codeVerifier); | ||
return url; | ||
} | ||
|
||
public async validateAuthorizationCode( | ||
code: string, | ||
codeVerifier: string | ||
): Promise<OAuth2Tokens> { | ||
const context = new AuthorizationCodeTokenRequestContext(code); | ||
context.authenticateWithHTTPBasicAuth(this.clientId, this.clientSecret); | ||
context.setRedirectURI(this.redirectURI); | ||
context.setCodeVerifier(codeVerifier); | ||
const tokens = await sendTokenRequest(this.tokenEndpoint, context); | ||
return tokens; | ||
} | ||
|
||
public async refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens> { | ||
const context = new RefreshRequestContext(refreshToken); | ||
context.authenticateWithHTTPBasicAuth(this.clientId, this.clientSecret); | ||
const tokens = await sendTokenRequest(this.tokenEndpoint, context); | ||
return tokens; | ||
} | ||
|
||
public async revokeToken(token: string): Promise<void> { | ||
const context = new TokenRevocationRequestContext(token); | ||
context.authenticateWithHTTPBasicAuth(this.clientId, this.clientSecret); | ||
await sendTokenRevocationRequest(this.tokenRevocationEndpoint, context); | ||
} | ||
} |