Skip to content

Commit

Permalink
Merge pull request #17 from danestves/main
Browse files Browse the repository at this point in the history
feat: support for `screen_hint` on authorize
  • Loading branch information
meza authored Mar 16, 2023
2 parents ab49b8f + 3535f2a commit a1050aa
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 9 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ to bridge that gap and also provide a convenient interface to use.
- [ ] see if we can handle the callback while maintaining the session from before the login
- [ ] create the callbacks for the id token and the refresh tokens
- [ ] opt out of the session handling
- [ ] enable register with passing ?screen_hint=signup to the authorize endpoint

## How to use

Expand Down Expand Up @@ -102,8 +101,11 @@ import { authenticator } from '../../auth.server';
import type { ActionFunction } from '@remix-run/node';

export const action: ActionFunction = () => {
const forceLogin = false; // set to true to force auth0 to ask for a login
authenticator.authorize(forceLogin);
authenticator.authorize();
// or
authenticator.authorize({ prompt: 'login' }) // force login
// or
authenticator.authorize({ screen_hint: 'signup' }) // force signup
};
```

Expand Down
12 changes: 12 additions & 0 deletions src/Auth0RemixTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ export interface Auth0RemixOptions {
credentialsCallback?: Auth0CredentialsCallback;
}

export interface AuthorizeOptions {
/**
* Specifying to 'none' is the same as not specifying it.
*/
prompt?: 'login' | 'none';
/**
* Is used to provide a hint to the server about the type of user interface that should be
* displayed during the authentication flow.
*/
screenHint?: 'signup';
}

export interface HandleCallbackOptions {
onSuccessRedirect?: string;
}
2 changes: 2 additions & 0 deletions src/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ exports[`Auth0 Remix Server > the authorization process > adds the organisation

exports[`Auth0 Remix Server > the authorization process > forces the login if asked 1`] = `"https://test.domain.com/authorize?response_type=code&response_mode=form_post&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth0%2Fcallback&scope=offline_access+openid+profile+email&audience=https%3A%2F%2Ftest.domain.com%2Fapi%2Fv2%2F&prompt=login"`;

exports[`Auth0 Remix Server > the authorization process > forces the signup if asked 1`] = `"https://test.domain.com/authorize?response_type=code&response_mode=form_post&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth0%2Fcallback&scope=offline_access+openid+profile+email&audience=https%3A%2F%2Ftest.domain.com%2Fapi%2Fv2%2F&screen_hint=signup"`;

exports[`Auth0 Remix Server > the authorization process > redirects to the authorization endpoint 1`] = `"https://test.domain.com/authorize?response_type=code&response_mode=form_post&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth0%2Fcallback&scope=offline_access+openid+profile+email&audience=https%3A%2F%2Ftest.domain.com%2Fapi%2Fv2%2F"`;
15 changes: 14 additions & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,20 @@ describe('Auth0 Remix Server', () => {
it<LocalTestContext>('forces the login if asked', ({ authOptions }) => {
const authorizer = new Auth0RemixServer(authOptions);

expect(() => authorizer.authorize(true)).toThrowError(redirectError); // a redirect happened
expect(() => authorizer.authorize({
prompt: 'login'
})).toThrowError(redirectError); // a redirect happened

const redirectUrl = vi.mocked(redirect).mock.calls[0][0];
expect(redirectUrl).toMatchSnapshot();
});

it<LocalTestContext>('forces the signup if asked', ({ authOptions }) => {
const authorizer = new Auth0RemixServer(authOptions);

expect(() => authorizer.authorize({
screenHint: 'signup'
})).toThrowError(redirectError); // a redirect happened

const redirectUrl = vi.mocked(redirect).mock.calls[0][0];
expect(redirectUrl).toMatchSnapshot();
Expand Down
48 changes: 43 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import type {
SessionStore,
UserCredentials,
UserProfile,
TokenError
TokenError,
AuthorizeOptions
} from './Auth0RemixTypes.js';
import type { AppLoadContext } from '@remix-run/node';

Expand All @@ -21,7 +22,6 @@ import type { AppLoadContext } from '@remix-run/node';
* - [ ] failed things should remove the user from the session
* - [ ] see if we can handle the callback while maintaining the session from before the login
* - [ ] opt out of the session handling
* - [ ] enable register with passing ?screen_hint=signup to the authorize endpoint
*/

export enum Token {
Expand Down Expand Up @@ -107,7 +107,42 @@ export class Auth0RemixServer {
}
}

public authorize(forceLogin = false) {
/**
* Initiates the Auth0 login/signup process by redirecting the user to the Auth0 authorization URL.
*
* @param {AuthorizeOptions} options - An object containing options for the authorization request.
* @param {string} [options.prompt] - Specifies whether to prompt the user to login or not. Valid values are 'login' and 'none'.
* @param {string} [options.screenHint] - Provides a hint to the server about the type of user interface to display.
*
* @throws {RedirectException} - Throws a `RedirectException` containing the authorization URL to redirect to.
*
* @description
* This function constructs the Auth0 authorization URL with the necessary parameters and redirects the user to that URL.
* The `prompt` parameter can be used to specify whether to prompt the user to login or not. If `prompt` is not specified,
* the default behavior is to prompt the user to login. The `screenHint` parameter can be used to provide a hint to the server
* about the type of user interface to display during the authentication flow (e.g. 'signup').
*
* If the `prompt` is set to `none` or is undefined, it will trigger a **silent authentication** flow:
*
* Silent authentication lets you perform an authentication flow where Auth0 will only reply with redirects, and never with a login page.
* When an Access Token has expired, silent authentication can be used to retrieve a new one without user interaction, assuming the user's
* Single Sign-on (SSO) session has not expired.
*
* ---
*
* Combining the `prompt` and `screenHint` parameters to control the behavior of the authorization request produce the following results:
*
* | parameter | No existing session | Existing session |
* |-----------|---------------------|------------------|
* | `screen_hint=signup` | Shows the signup page | Redirects to the callback url |
* | `prompt=login` | Shows the login page | Shows the login page |
* | `prompt=login&screen_hint=signup` | Shows the signup page | Shows the signup page |
*
* @see https://auth0.com/docs/api/authentication
* @see https://auth0.com/docs/api-auth/tutorials/silent-authentication
* @see https://auth0.com/docs/authenticate/login/auth0-universal-login/new-experience#signup
*/
public authorize({ prompt, screenHint }: AuthorizeOptions = {}) {
const scope = [
'offline_access', // required for refresh token
'openid', // required for id_token and the /userinfo api endpoint
Expand All @@ -120,8 +155,11 @@ export class Auth0RemixServer {
authorizationURL.searchParams.set('redirect_uri', this.callbackURL);
authorizationURL.searchParams.set('scope', scope.join(' '));
authorizationURL.searchParams.set('audience', this.clientCredentials.audience);
if (forceLogin) {
authorizationURL.searchParams.set('prompt', 'login');
if (prompt) {
authorizationURL.searchParams.set('prompt', prompt);
}
if (screenHint) {
authorizationURL.searchParams.set('screen_hint', screenHint);
}
if (this.clientCredentials.organization) {
authorizationURL.searchParams.set('organization', this.clientCredentials.organization);
Expand Down

0 comments on commit a1050aa

Please sign in to comment.