This guide is about Authentication for Web and Mobile Apps.
After reading this guide you will
Slides - use arrow keys to navigate, esc to return to page view, f for fullscreen
Some questions to ask yourself:
any combination of:
When creating command line programs, web apps, native apps we have different possibilities.
You already know how to use HTTP Cookies for authentication in a web app. Command line programs and native app do not use cookies automatically as browser do.
To illustrate this let's look at a command line program.
Here an example of an authentication flow for the office365 command line tool:
the program opens a browser at a special Microsoft login page and asks you to type in a short string there.
go throug the usual steps of login
final screen in the browser shows success
back in the command line program you are now authorized.
When thinking about Authentication and Web Applications we first have to overcome the stateless nature of HTTP. There are several ways to do this:
This is the oldest method. It still works in all the browser, but is not used much any more because the UI is very restrictive.
It is specified in RFC 1945, section 11.
WWW-Authenticate: Basic ...
header in the response, but not the resource;Authorization: Basic ...
This can be configured in the web server.
HTTP Cookies are defined in RFC 6265.
Cookie
).Cookies are often integrated into backend frameworks as a method to identify sessions.
This method uses the same HTTP Header Authorization
as HTTP Basic authentication,
rfc 1945, section 11.
The client sends the token with the Authorization: Bearer ...
HTTP header.
Web Authentication, or short "WebAuthn" is a W3C standard that has been implemented in all Browsers since 2020.
You a User you have to own an authenticator: for example a YubiKey, USB Token, or your smartphone. This Authenticator is a powerful "second factor": it can do cryptographic computation and it can store data.
This diagram from auth0 shows the players in this form auf authentication:
The server you want to log into is called the "relying party".
During Registration the relying party supplies data called "challenge". The JavaScript
in the users browser calls navigator.credentials.create()
with this challenge. This makes
the browser call the authenticator device with the challenge data. The device might ask the user
for some form of consent, for example given through a fingerprint or a touch sensor.
The result is a public/private key pair.
During Authentication (or "attestation") the same challenge from the relying party is given to the authenticator.
See also
OpenID 2.0 is an open standard for Authentication.
OAuth is and open standard for API Access delegation originally published in 2010. The current version is OAuth 2.0, published as RFC 6749 and RFC 6750 in 2012.
Both follow the same flow:
JSON-Web-Token are a way to encode and sign JSON-Data. You can use many transmission methods to send them:
Authorization: Bearer ...
or Cookie
POST data
A JWT consists of three parts: header, payload and signature. All three are encoded and concatenated with a dot. The result looks like this (if you color-code it):
The encoding consists of two steps:
{ "msg_en": "Hello",
"msg_jp": "こんにちは"
"msg_de": "Guten Tag" }
eyAibXNnX2VuIjogIkhlbGxvIiwKICAibXNnX2pwIjogIuOBk+OCk+OBq+OBoeOBryIKICAibXNnX2RlIjogIkd1dGVuIFRhZyIgfQ==
eyAibXNnX2VuIjogIkhlbGxvIiwKICAibXNnX2pwIjogIuOBk-OCk-OBq-OBoeOBryIKICAibXNnX2RlIjogIkd1dGVuIFRhZyIgfQ
You can use the JWT Debugger to decode this.
The gem omniauth
helps you deal with OAuth2, OpenID, LDAP, and many
other authentication providers. The list of strategies
is quite impressive. Think carefully about what services your users
are using, and which services might be useful to your app: could
you use Dropbox to authenticate, and also to deliver data directly
to your user's dropbox? Would it make sense to use Facebook or Twitter and also
send out messages that way? Or are your users very privacy conscious and
want to avoid Facebook and Google?
You will need the Gem omniauth
and
additional gems for each provider. For example if you
want to use both Github and Stackoverflow for your web app geared
towards developers, you would need three gems:
gem 'omniauth'
gem 'omniauth-github'
gem 'omniauth-stackoverflow'
Use the gems devise
and devise-passkeys
Use the gem jwt
- devise_token_auth for token based authentication for API only Rails apps
The package next-auth
provides several authentication methods.
And it can save information to a database through an adapter.
When you install the package you also need to set several
environment variables (for example through .env
):
The URL of the application - this is needed to construct callback urls
for OAuth2. And a secret that will be used to sign certain tokens.
You can use openssl rand -base64 32
to create a random string.
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=...
For OAuth2 you have to configure two parties at the same time:
You need to finde the right webpage that lets you configure the authentication provider. For example at Github you find it under Settings, "for Developers", "create App".
You need to supply the following information:
https://myapp.at
https://myapp.at/api/auth/callback/github
In the documentation for auth-next
you can see that the callback URL has the form /api/auth/callback/:provider
When using the app router, you need to create the file app/api/auth/[...nextauth]/route.js
Here you need to import and configure one or several authentication providers.
This example shows github. See node_modules/next-auth/providers/*.js
for a
list of the available providers.
// File app/api/auth/[...nextauth]/route.js
import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
export const authOptions = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
})
]
};
const handler = NextAuth(authOptions);
export {
handler as GET,
handler as POST
};
// in a server component - no access to state
import {authOptions} from "@/app/api/auth/[...nextauth]/route.js"
import {getServerSession} from "next-auth/next"
import Link from "next/link"
export default async function SignInStatus() {
const session = await getServerSession(authOptions)
if(session) {
return <>
Signed in as {session.user.email} <br/>
<Link href="/api/auth/signout">Sign out</Link>
<pre>{JSON.stringify(session, null, 2)}</pre>
</>
}
return <>
Not signed in <br/>
<Link href="/api/auth/signin">Sign in</Link>
</>
}
// in a server component - no access to state
import {authOptions} from "@/app/api/auth/[...nextauth]/route.js"
import {getServerSession} from "next-auth/next"
export default async function ServerComponent() {
const session = await getServerSession(authOptions)
...
}
To have the session available in client components, we first
have to wrap the whole app in a SessionProvider
. This needs
to happen in a client component. But how can we use
server components inside this client component?
We need three steps to get there:
layout.js
ClientSessionProvider.client.js
We start off in layout.js
in the main app
folder, and use
the method of wrapping server components inside a client component
inside a server component mentioned before.
// app/layout.js
import ClientSessionProvider from '@/components/ClientSessionProvider.client.js'
export default function RootLayout({ children }) {
return (
<html lang="en">
<ClientSessionProvider>
<body className={inter.className}>
{children}
</body>
</ClientSessionProvider>
</html>
)
}
Then inside the client component we use the session provider:
// components/ClientSessionProvider.client.js
"use client"
import { SessionProvider } from "next-auth/react"
export default function ClientSessionProvider({ children }) {
return (
<SessionProvider>
{children}
</SessionProvider>
)
}
Now we can use the session in any client component:
// components/SomeComponent.client.js
import { useSession } from "next-auth/react"
export default function SomeComponent({}) {
const { data: session, status } = useSession();
...
return (
<>
<h1>{ status !== "authenticated" ? status : JSON.stringify(session)}</h1>
</>
)
}