remix-auth-okta
Advanced tools
Comparing version 1.0.0 to 1.1.0
import { SessionStorage } from "@remix-run/server-runtime"; | ||
import { AuthenticateOptions, StrategyVerifyCallback } from "remix-auth"; | ||
import { OAuth2Profile, OAuth2Strategy, OAuth2StrategyOptions, OAuth2StrategyVerifyParams } from "remix-auth-oauth2"; | ||
import { OAuth2Strategy, OAuth2StrategyOptions, OAuth2StrategyVerifyParams } from "remix-auth-oauth2"; | ||
export interface OktaProfile { | ||
@@ -15,11 +15,11 @@ provider: string; | ||
} | ||
export declare type OktaStrategyOptions = Omit<OAuth2StrategyOptions, "authorizationURL" | "tokenURL" | "callbackURL"> & { | ||
export declare type OktaStrategyOptions = Omit<OAuth2StrategyOptions, "authorizationURL" | "tokenURL"> & { | ||
scope?: string; | ||
issuer: string; | ||
} & ({ | ||
flow: "Password"; | ||
callbackURL: never; | ||
withCustomLoginForm: true; | ||
oktaDomain: string; | ||
} | { | ||
flow?: "Code"; | ||
callbackURL: string; | ||
withCustomLoginForm?: false; | ||
oktaDomain?: never; | ||
}); | ||
@@ -30,8 +30,12 @@ export declare type OktaExtraParams = Record<string, string | number>; | ||
private userInfoURL; | ||
private authenticationURL; | ||
private readonly scope; | ||
private readonly flow; | ||
constructor({ issuer, scope, clientID, clientSecret, ...rest }: OktaStrategyOptions, verify: StrategyVerifyCallback<User, OAuth2StrategyVerifyParams<OAuth2Profile, OktaExtraParams>>); | ||
private readonly withCustomLoginForm; | ||
private sessionToken; | ||
constructor({ issuer, scope, clientID, clientSecret, callbackURL, ...rest }: OktaStrategyOptions, verify: StrategyVerifyCallback<User, OAuth2StrategyVerifyParams<OktaProfile, OktaExtraParams>>); | ||
authenticate(request: Request, sessionStorage: SessionStorage, options: AuthenticateOptions): Promise<User>; | ||
private getCallbackURLFrom; | ||
private getSessionTokenWith; | ||
protected authorizationParams(): URLSearchParams; | ||
protected userProfile(accessToken: string): Promise<OktaProfile>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.OktaStrategy = void 0; | ||
const server_runtime_1 = require("@remix-run/server-runtime"); | ||
const remix_auth_oauth2_1 = require("remix-auth-oauth2"); | ||
class OktaStrategy extends remix_auth_oauth2_1.OAuth2Strategy { | ||
constructor({ issuer, scope = "openid profile email", clientID, clientSecret, ...rest }, verify) { | ||
var _a; | ||
constructor({ issuer, scope = "openid profile email", clientID, clientSecret, callbackURL, ...rest }, verify) { | ||
super({ | ||
@@ -13,18 +13,74 @@ authorizationURL: `${issuer}/v1/authorize`, | ||
clientSecret, | ||
callbackURL: rest.flow === "Password" ? "" : rest.callbackURL, | ||
callbackURL, | ||
}, verify); | ||
this.name = "okta"; | ||
this.sessionToken = ""; | ||
this.scope = scope; | ||
this.userInfoURL = `${issuer}/v1/userinfo`; | ||
this.flow = (_a = rest.flow) !== null && _a !== void 0 ? _a : "Code"; | ||
this.authenticationURL = `${issuer}/api/v1/authn`; | ||
this.withCustomLoginForm = !!rest.withCustomLoginForm; | ||
this.authenticationURL = rest.withCustomLoginForm | ||
? `${rest.oktaDomain}/api/v1/authn` | ||
: ""; | ||
} | ||
authenticate(request, sessionStorage, options) { | ||
if (this.flow === "Code") { | ||
async authenticate(request, sessionStorage, options) { | ||
var _a; | ||
if (!this.withCustomLoginForm) { | ||
return super.authenticate(request, sessionStorage, options); | ||
} | ||
throw new Error("not implemented"); | ||
let session = await sessionStorage.getSession(request.headers.get("Cookie")); | ||
let user = (_a = session.get(options.sessionKey)) !== null && _a !== void 0 ? _a : null; | ||
if (user) { | ||
return this.success(user, request.clone(), sessionStorage, options); | ||
} | ||
const url = new URL(request.url); | ||
const callbackUrl = this.getCallbackURLFrom(url); | ||
if (url.pathname !== callbackUrl.pathname) { | ||
const form = await request.formData(); | ||
const email = form.get("email"); | ||
const password = form.get("password"); | ||
if (!email || !password) { | ||
throw (0, server_runtime_1.json)({ message: "Bad request, missing email and password." }, { status: 400 }); | ||
} | ||
this.sessionToken = await this.getSessionTokenWith(email.toString(), password.toString()); | ||
} | ||
return super.authenticate(request, sessionStorage, options); | ||
} | ||
getCallbackURLFrom(url) { | ||
if (this.callbackURL.startsWith("http:") || | ||
this.callbackURL.startsWith("https:")) { | ||
return new URL(this.callbackURL); | ||
} | ||
if (this.callbackURL.startsWith("/")) { | ||
return new URL(this.callbackURL, url); | ||
} | ||
return new URL(`${url.protocol}//${this.callbackURL}`); | ||
} | ||
async getSessionTokenWith(email, password) { | ||
let response = await fetch(this.authenticationURL, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
username: email, | ||
password, | ||
}), | ||
}); | ||
if (!response.ok) { | ||
try { | ||
let body = await response.text(); | ||
throw new Error(body); | ||
} | ||
catch (error) { | ||
throw error; | ||
} | ||
} | ||
const data = await response.json(); | ||
return data.sessionToken; | ||
} | ||
authorizationParams() { | ||
return new URLSearchParams({ | ||
scope: this.scope, | ||
sessionToken: this.sessionToken, | ||
}); | ||
@@ -31,0 +87,0 @@ } |
{ | ||
"name": "remix-auth-okta", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"main": "./build/index.js", | ||
@@ -5,0 +5,0 @@ "types": "./build/index.d.ts", |
@@ -0,1 +1,3 @@ | ||
![CI](https://img.shields.io/github/workflow/status/jrakotoharisoa/remix-auth-okta/CI?style=flat-square) | ||
![npm](https://img.shields.io/npm/v/remix-auth-okta?style=flat-square) | ||
# OktaStrategy | ||
@@ -13,5 +15,3 @@ | ||
<!-- If it doesn't support one runtime, explain here why --> | ||
## How to use | ||
## Prerequisites | ||
### Create an Okta Web app | ||
@@ -21,4 +21,6 @@ | ||
### Create the strategy instance | ||
## How to use | ||
#### Create the strategy instance | ||
```typescript | ||
@@ -92,1 +94,81 @@ // app/utils/auth.server.ts | ||
``` | ||
## How to use with custom login page | ||
#### Create the strategy instance | ||
```typescript | ||
// app/utils/auth.server.ts | ||
import { Authenticator } from "remix-auth"; | ||
import { OktaStrategy } from "remix-auth-okta"; | ||
// Create an instance of the authenticator, pass a generic with what your | ||
// strategies will return and will be stored in the session | ||
export const authenticator = new Authenticator<User>(sessionStorage); | ||
let oktaStrategy = new OktaStrategy( | ||
{ | ||
// example of issuer: https://dev-1234.okta.com/oauth2/default | ||
issuer: "YOUR_OKTA_ISSUER", | ||
clientID: "YOUR_OKTA_CLIENT_ID", | ||
clientSecret: "YOUR_OKTA_CLIENT_SECRET", | ||
callbackURL: "https://your-app-domain.com/auth/okta/callback", | ||
// Add this to options for custom login form | ||
withCustomLoginForm: true, | ||
// example of okta domain: https://dev-1234.okta.com | ||
oktaDomain: "YOUR_OKTA_DOMAIN" | ||
}, | ||
async ({ accessToken, refreshToken, extraParams, profile }) => { | ||
// Get the user data from your DB or API using the tokens and profile | ||
return User.findOrCreate({ email: profile.email }); | ||
} | ||
); | ||
authenticator.use(oktaStrategy); | ||
``` | ||
### Setup your routes | ||
```typescript | ||
// app/routes/login.tsx | ||
export default function Login() { | ||
return ( | ||
<Form action="/auth/okta" method="post"> | ||
<input type="text" name="email"/> | ||
<input type="password" name="password"/> | ||
<button>Log in</button> | ||
</Form> | ||
); | ||
} | ||
``` | ||
```typescript | ||
// app/routes/auth/okta.tsx | ||
import type { ActionFunction, LoaderFunction } from "remix"; | ||
import { authenticator } from "~/utils/auth.server"; | ||
export let loader: LoaderFunction = () => redirect("/login"); | ||
export let action: ActionFunction = ({ request }) => { | ||
return authenticator.authenticate("okta", request); | ||
}; | ||
``` | ||
```typescript | ||
// app/routes/auth/okta/callback.tsx | ||
import type { ActionFunction, LoaderFunction } from "remix"; | ||
import { authenticator } from "~/utils/auth.server"; | ||
export let loader: LoaderFunction = ({ request }) => { | ||
return authenticator.authenticate("okta", request, { | ||
successRedirect: "/private", | ||
failureRedirect: "/login", | ||
}); | ||
}; | ||
``` |
13010
147
172
2