OpenID Connect (server side)
Oneki.js supports the Open ID Connect (OIDC) authorization code flow where the authorization code is exchanged for an access token via a server. This is the most common and secure way to retrieve an access token
In settings.ts, when the "idp type" is "oidc_server", Oneki.js implements the following scenario:
To authenticate against an OpenID Connect Identity Provider (OIDC IDP), you have to create four pages/routes:
- login: this page is displayed following a click on a link or a redirect following a 401 HTTP Error
- login callback: this route is called by the OIDC IDP (e.g: Google) after a successfull authentication
- logout: this page is displayed following a click on a logout link or if there is no activity for x minutes (configurable via settings)
- logout callback: this route is called by the OIDC IDP after a successfull logout
The code is the same for a NextJS App or a Create React App
Examples
Login with Google
GoogleLoginPage.tsx
(/auth/login/google
)Redirects to: https://accounts.google.com/o/oauth2/v2/auth?scope=openid%20email%20profile
&client_id=519201240542-gk79ts8svme25ve4sfuoksjvdupv7fhe.apps.googleusercontent.com&response_type=code
&redirect_uri=https://examples.oneki.net/auth/login/google/callback
&state=b3f4f525792470ca73732b4f4681809ed769e3429d782298037c63dfec92cfda
&code_challenge=mt2n-8AucWai57be_wj6L0iScRrZoXO3t9EevEYLvw0
&code_challenge_method=S256
Once the user is authenticated, Google redirects the user to the page handled by LoginCallbackPage.tsx
(/auth/login/google/callback
) and provides an authorization code as a query parameter.
Redirects to: https://examples.oneki.net/auth/login/google/callback?authuser=0
&state=363be2fcb87af1f01cc45d6d7df0e73f0986b1ef3c4d77e5e635e2d452301957
&code=4%2F0Adeu5BUgbCt1aswdkBfCC0WxoKk8Fj8TaJwPdD5DAwHOcuxAfTwPY_kimKgBTgwL0yRldA
&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile
&prompt=none
LoginCallbackPage.tsx
exchanges the authorization code to an JWT access token by calling a backend APIPOST /api/oauth2/google/token
Host: https://examples.oneki.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 451
grant_type=authorization_code&
client_id=519201240542-gk79ts8svme25ve4sfuoksjvdupv7fhe.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fexamples.oneki.net%2Fauth%2Flogin%2Fgoogle%2Fcallback&
code=4%2F0Adeu5BUgbCt1aswdkBfCC0WxoKk8Fj8TaJwPdD5DAwHOcuxAfTwPY_kimKgBTgwL0yRldA&
scope=openid%20email%20profile&code_verifier=vfPr9~ZNJ1msSAaMdC-xvymeXoAYJ.6QWRTw~.0-79UXRE11kOqfcegLLXzmpJ6-GO1RtVCD91_GdyvGgmiSWw9LnhX7__PtjHfwE2Kjasit0se_~h0Om-hpiwzgoL
The response contains the access and id tokens
{
"access_token": "ya29.a0AfB_byA6ryWtal5OpY....mDLi_QZeBBQ8-anlgA0165",
"expires_in": 3597,
"scope": "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsImt....wlZltc2dpTuSMvnvFgGURbgUTWg"
}
The JWT token is stored in the local storage because settings.ts
contains the following configuration: idp.google.persist: 'localStorage' (Defaults to: stored only in the global state)
- GoogleLoginPage.tsx
- LoginCallbackPage.tsx
- settings.ts
- Backend API
- 👁 Preview
loading...
loading...
loading...
loading...
Logout (external logout with callback)
- Logout
- Logout Callback
- Settings
- 👁 Preview
loading...
loading...
loading...
- Login
- Login Callback
- Logout
- Logout Callback
import React from "react";
import { useLoginService } from "onekijs";
export default React.memo(() => {
const idpName = 'google';
const options = {};
const [error] = useLoginService(idpName, options);
if (error) {
return <div>{error.payload.message} <span onClick={() => error.remove()}>X</span></div>
}
return null;
}
Parameters
Inputs
// [Optional] the name of the IDP used for the login -- defaults to "default"
idpName: string
// [Optional] options object -- defaults to {}
options: {
// a callback function triggered when an error is thrown -- defaults to send error on topic "login-error"
onError: func
}
Outputs
error: {
payload: {
// description of the error
message: string,
// code of the error
code: string,
}
// remove the error
remove: func
}
import React from "react";
import { useLoginCallbackService } from "onekijs";
export default React.memo(() => {
const idpName = 'google';
const options = {};
const [error] = useLoginCallbackService(idpName,options);
if (error) {
return <div>{error.payload.message} <span onClick={() => error.remove()}>X</span></div>
}
return null;
}
Parameters
Inputs
// [Optional] the name of the IDP used for the login -- defaults to "default"
idpName: string
// [Optional] options object -- defaults to {}
options: {
// a callback function triggered when an the login is successfull -- The default redirects the user from the calling page
onSuccess: func
// a callback function triggered when an error is thrown -- defaults to send error on topic "login-error"
onError: func
}
Outputs
error: {
payload: {
// description of the error
message: string,
// code of the error
code: string,
}
// remove the error
remove: func
}
import React from "react";
import { useLogoutService } from "onekijs";
export default React.memo(() => {
const options = {};
const [error] = useLogoutService(options);
if (error) {
return <div>{error.payload.message} <span onClick={() => error.remove()}>X</span></div>
}
return null;
}
Parameters
Inputs
// [Optional] options object -- defaults to {}
options: {
// a callback function triggered when an error is thrown -- defaults to send error on topic "logout-error"
onError: func
}
Outputs
error: {
payload: {
// description of the error
message: string,
// code of the error
code: string,
}
// remove the error
remove: func
}
import React from "react";
import { useLoginCallbackService } from "onekijs";
export default React.memo(() => {
const options = {};
const [error] = useLogoutCallbackService(options);
if (error) {
return <div>{error.payload.message} <span onClick={() => error.remove()}>X</span></div>
}
return null;
}
Parameters
Inputs
// [Optional] options object -- defaults to {}
options: {
// a callback function triggered when an the logout is successfull -- The default redirects the user to the home page
onSuccess: func
// a callback function triggered when an error is thrown -- defaults to send error on topic "logout-error"
onError: func
}
Outputs
error: {
payload: {
// description of the error
message: string,
// code of the error
code: string,
}
// remove the error
remove: func
}
Configuration
useLoginService, useLoginCallbackService, useLogoutService, useLogoutCallbackService are fully configured in settings.js
The configuration must be defined under the key "idp/:idpName". For example, if idpName=google, the config must look like this:
const settings = {
idp: {
google: {
type: "oidc_server",
...
}
}
}
Mandatory attributes
Key | Type | Description |
---|---|---|
authorizeEndpoint | string | function(context) | Can be
|
clientId | string | the client_id created on the IDP (identity provider) |
logoutEndpoint | string | function(context) | Can be
|
tokenEndpoint | string | function(grant_type, context) | Can be
|
type | string | must be "oidc_server" |
userinfoEndpoint | string | function (context) | Can be:
|
Optional attributes
Key | Type | Description | Default |
---|---|---|---|
callback | function(response, context): [token,userInfo] | Callback called at the end of the authentication for extracting the token and the userInfo from the response. Inputs
| null |
codeChallengeMethod | string | Method that was used to derive an authorization code challenge | S256 |
jwksEndpoint | string | function(token, context) | jwksEndpoint is mandatory if validate = true. Can be
| null |
loginCallbackRoute | string | a relative or absolute URL called by the OIDC server after a successfull login. | [loginRoute]/callback |
logoutCallbackRoute | string | a relative or absolute URL called by the OIDC server after a successfull logout. Should be used to remove the cookie on the server side | [logoutRoute]/callback |
pkce | boolean | flag to indicate if the PKCE extension is applied. Recommended | true |
nonce | boolean | flag to indicate if the nonce in the id_token is validated on the client side. Should be done on the server side | false |
postLoginRedirectKey | string | When calling the authorize endpoint, postLoginRedirectKey represents the name of the parameter to indicate the redirect URI | redirect_uri |
postLogoutRedirectKey | string | When calling the logout endpoint, postLoginRedirectKey represents the name of the parameter to indicate the redirect URI | post_logout_redirect_uri |
responseType | string | only code is supported right now | code |
scope | string | the value of the parameter "scope" sent to the authorize endpoint. Should generally be redefined | openid |
state | boolean | flag to indicate if the javascript client send a state to the IDP. Recommended for mitigating attacks | true |
validate | boolean | flag to indicate if the id_token and the access_token are validated. Should generally be done on the server side | false |
Configuration example
const settings = {
idp: {
google: {
type: 'oidc_server',
clientId: '1eb5cq6p7d8dm8g4q9jk6qdve5', // id given by Google
authorizeEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', // URL given by Google. Will be called by the client
tokenEndpoint: '/api/oauth2/token', // URL of a service exposed by your server that exchanges the authorization code for an access token by calling the Google /token endpoint
userinfoEndpoint: '/api/oauth2/userinfo', // URL of a service exposed by your server that returns the details about the logged-in user
logoutEndpoint: '/api/oauth2/logout', // URL exposed by your server which call the IDP logout URL and then removes the cookie
scope: 'openid email profile', // ask to Google the profile and the email of the user
}
}
}